<commit_before>'''
Created on Oct 20, 2014

@author: alfoa

This module contains interfaces to import external functions
'''
#for future compatibility with Python 3--------------------------------------------------------------

#End compatibility block for Python 3----------------------------------------------------------------

#External Modules------------------------------------------------------------------------------------

#External Modules End--------------------------------------------------------------------------------

#Internal Modules------------------------------------------------------------------------------------
from BaseClasses import BaseType
import utils
from CustomCommandExecuter import execCommand
#import Datas
#Internal Modules End--------------------------------------------------------------------------------

class Function(BaseType):
  '''
  This class is the base class for different wrapper for external functions
  provide the tools to solve
  F(x)=0 where at least one of the component of the output space of a model (F is a vector as x)
  plus a series of inequality in the input space that are considered by the supportBoundingTest
  '''
  def __init__(self):
    BaseType.__init__(self)
    self.__functionFile                  = ''                                # function file name
    self.__actionDictionary              = {}                                # action dictionary
    self.__actionImplemented             = {}                                # dictionary of implemented actions
    self.__inputVariables                = []                                # list of variables' names' given in input (xml)
    self.__inputFromWhat                 = {}                                # dictionary of input data type
    self.__inputFromWhat['dict']         = self.__inputFromDict
    self.__inputFromWhat['Data']         = self.__inputFromData
    self.printTag                        = utils.returnPrintTag('FUNCTIONS')
  def _readMoreXML(self,xmlNode,debug=False):
    if 'file' in xmlNode.attrib.keys():
      self.functionFile = xmlNode.attrib['file']
      if self.functionFile.endswith('.py') : moduleName = ''.join(self.functionFile.split('.')[:-1]) #remove the .py
      else: moduleName = self.functionFile
      importedModule = utils.importFromPath(moduleName)
      if not importedModule: raise IOError('Failed to import the module '+moduleName+' supposed to contain the function: '+self.name)
      #here the methods in the imported file are brought inside the class
      for method in importedModule.__dict__.keys():
        if method in ['__residuumSign__','__residuumSign','residuumSign',
                      '__supportBoundingTest__','__supportBoundingTest','supportBoundingTest',
                      '__residuum__','__residuum','residuum','__gradient__','__gradient','gradient']:
          if method in ['__residuumSign__','__residuumSign','residuumSign']:
            self.__residuumSign                                =  importedModule.__dict__[method]
            self.__actionDictionary['residuumSign' ]           = self.__residuumSign
            self.__actionImplemented['residuumSign']           = True
          else:self.__actionImplemented['residuumSign']        = False
          if method in ['__supportBoundingTest__','__supportBoundingTest','supportBoundingTest']:
            self.__supportBoundingTest                         =  importedModule.__dict__[method]
            self.__actionDictionary['supportBoundingTest' ]    = self.__supportBoundingTest
            self.__actionImplemented['supportBoundingTest']    = True
          else:self.__actionImplemented['supportBoundingTest'] = False
          if method in ['__residuum__','__residuum','residuum']:
            self.__residuum                                    =  importedModule.__dict__[method]
            self.__actionDictionary['residuum' ]               = self.__residuum
            self.__actionImplemented['residuum']               = True
          else:self.__actionImplemented['residuum']            = False
          if method in ['__gradient__','__gradient','gradient']:
            self.__gradient                                    =  importedModule.__dict__[method]
            self.__actionDictionary['gradient']                = self.__gradient
            self.__actionImplemented['gradient']               = True
          else:self.__actionImplemented['gradient']            = False
        else:
          #custom
          self.__actionDictionary[method]                    = importedModule.__dict__[method]
          self.__actionImplemented[method]                   = True
    else: raise IOError(self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> No file name for the external function has been provided for external function '+self.name+' of type '+self.type)
    cnt = 0
    for child in xmlNode:
      if child.tag=='variable':
        execCommand('self.'+child.text+' = None',self=self)
        self.__inputVariables.append(child.text)
        cnt +=1
        if len(child.attrib.keys()) > 0: raise IOError( self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> variable block in the definition of the function '+self.name + ' should not have any attribute!')
    if cnt == 0: raise IOError( self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> not variable found in the definition of the function '+self.name)

  def addInitParams(self,tempDict):
    '''
    This function is called from the base class to print some of the information inside the class.
    Whatever is permanent in the class and not inherited from the parent class should be mentioned here
    The information is passed back in the dictionary. No information about values that change during the simulation are allowed
    @ In/Out tempDict: {'attribute name':value}
    '''
    tempDict['Module file name'                    ] = self.functionFile
    tempDict['The residuum is provided'            ] = self.__actionImplemented['residuum']
    tempDict['The sign of the residuum is provided'] = self.__actionImplemented['residuumSign']
    tempDict['The gradient is provided'            ] = self.__actionImplemented['gradient']
    tempDict['The support bonding is provided'     ] = self.__actionImplemented['supportBoundingTest']
    for key,value in enumerate(self.__actionImplemented):
      if key not in ['residualSign','supportBoundingTest','residual','gradient']: tempDict['Custom Function'] = value
  def addCurrentSetting(self,tempDict):
    '''
    This function is called from the base class to print some of the information inside the class.
    Whatever is a temporary value in the class and not inherited from the parent class should be mentioned here
    The information is passed back in the dictionary
    Function adds the current settings in a temporary dictionary
    @ In, tempDict
    @ Out, tempDict
    '''
    for key in self.__inputVariables: execCommand("object['variable "+str(key)+" has value']=self."+key,self=self,object=tempDict)

  def __importValues(self,myInput):
    '''this makes available the variable values sent in as self.key'''
    if type(myInput)==dict         :self.__inputFromWhat['dict'](myInput)
    elif 'Data' in myInput.__base__:self.__inputFromWhat['Data'](myInput)
    else: raise Exception (self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> Unknown type of input provided to the function '+str(self.name))

  def __inputFromData(self,inputData):
    '''
    This is meant to be used to collect the input from a Data. A conversion to the declared type of data is attempted by inputData.extractValue'''
    for key in self.__inputVariables:
      #exec('self.'+key+'=inputData.extractValue(myType,key)')
      ##### TEMPORARY FIXXXXXXXX - ALIAS NEEDED#######
      print('FIXME: Alias are already in place why we have still the fixme (once done see also if the loop should contain the myType???')
      foundperfectly = False
      for index in range(len(inputData.dataParameters['inParam'])):
        if key == inputData.dataParameters['inParam'][index]: foundperfectly = True
      if not foundperfectly:
        for index in range(len(inputData.dataParameters['outParam'])):
          if key == inputData.dataParameters['outParam'][index]: foundperfectly = True
      if foundperfectly: execCommand('self.'+key+'=object.extractValue(myType,"'+key+'")',self=self,object=inputData)
      if not foundperfectly:
        semifound = False
        for index in range(len(inputData.dataParameters['inParam'])):
          if key in inputData.dataParameters['inParam'][index]:
            similarVariable = inputData.dataParameters['inParam'][index]
            semifound = True
        if not semifound:
          for index in range(len(inputData.dataParameters['outParam'])):
            if key in inputData.dataParameters['outParam'][index]:
              similarVariable = inputData.dataParameters['outParam'][index]
              semifound = True
        if semifound: execCommand('self.'+key+'=object.extractValue(myType,"'+similarVariable+'")',self=self,object=inputData)

  def __inputFromDict(self,myInputDict):
    '''
    This is meant to be used to collect the input directly from a sampler generated input or simply from a generic dictionary
    In case the input come from a sampler the expected structure is myInputDict['SampledVars'][variable name] = value
    In case it is a generic dictionary the expected structure is myInputDict[variable name] = value
    '''
    if 'SampledVars' in myInputDict.keys(): inDict = myInputDict['SampledVars']
    else                                  : inDict = myInputDict
    for name in self.__inputVariables:
      if name in inDict.keys(): execCommand('self.'+name+'=object["'+name+'"]',self=self,object=inDict)
      else                    : raise Exception(self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> The input variable '+name+' in external function seems not to be passed in')

  def evaluate(self,what,myInput):
    '''return the result of the type of action described by 'what' '''
    self.__importValues(myInput)
    toBeReturned=self.__actionDictionary[what](self)
    return toBeReturned

  def evaluateArgs(self,method,args):
    '''Will take a method from a Function object and apply the list of arguments
       defined by args to the method evaluation
    '''
    toBeReturned=self.__actionDictionary[method](args)
    return toBeReturned

  def parameterNames(self,method=None):
    '''
    Just get a list of the variables this function needs, alternatively, you
    can pass in a specific method from this Function and it will give you just
    the arguments that function needs. In this way, an external function would
    not have to reference self, and could just have matching variable names, or
    better yet, the xml could define the aliasing from the external script to
    the internal data
    '''
    if method is None:
      return self.__inputVariables[:]
    else:
    # func_code.co_varnames and func_code.co_argcount are python built-ins that
    # expose the variables and argument count of a python function, this allows
    # us to return the names of the function parameters/arguments
      argCount = self.__actionDictionary[method].func_code.co_argcount
    # So, the first argCount elements of co_varnames will always be the function
    # input argument list
      return self.__actionDictionary[method].func_code.co_varnames[:argCount]

class PythonFunction(Function):
  def __init__(self):
    super(self.__class__, self).__init__()
  
  def evaluate(self,method,args):
    '''Will take a method from a Function object and apply the list of arguments
       defined by args to the method evaluation
    '''
    toBeReturned=self._Function__actionDictionary[method](**args)
    return toBeReturned

'''
 Interface Dictionary (factory) (private)
'''

__base = 'function'
__interFaceDict = {}
__interFaceDict['Basic']    = PythonFunction
__interFaceDict['External'] = Function
__knownTypes                = __interFaceDict.keys()


def knonwnTypes():
  return __knownTypes

def returnInstance(Type):
  '''This function return an instance of the request model type'''
  if Type in knonwnTypes():return __interFaceDict[Type]()
  else: raise NameError('not known '+__base+' type '+Type)<commit_msg>Adding a short description of the Function class I wrote to help people understand what it does and why.<commit_after>'''
Created on Oct 20, 2014

@author: alfoa

This module contains interfaces to import external functions
'''
#for future compatibility with Python 3--------------------------------------------------------------

#End compatibility block for Python 3----------------------------------------------------------------

#External Modules------------------------------------------------------------------------------------

#External Modules End--------------------------------------------------------------------------------

#Internal Modules------------------------------------------------------------------------------------
from BaseClasses import BaseType
import utils
from CustomCommandExecuter import execCommand
#import Datas
#Internal Modules End--------------------------------------------------------------------------------

class Function(BaseType):
  '''
  This class is the base class for different wrapper for external functions
  provide the tools to solve
  F(x)=0 where at least one of the component of the output space of a model (F is a vector as x)
  plus a series of inequality in the input space that are considered by the supportBoundingTest
  '''
  def __init__(self):
    BaseType.__init__(self)
    self.__functionFile                  = ''                                # function file name
    self.__actionDictionary              = {}                                # action dictionary
    self.__actionImplemented             = {}                                # dictionary of implemented actions
    self.__inputVariables                = []                                # list of variables' names' given in input (xml)
    self.__inputFromWhat                 = {}                                # dictionary of input data type
    self.__inputFromWhat['dict']         = self.__inputFromDict
    self.__inputFromWhat['Data']         = self.__inputFromData
    self.printTag                        = utils.returnPrintTag('FUNCTIONS')
  def _readMoreXML(self,xmlNode,debug=False):
    if 'file' in xmlNode.attrib.keys():
      self.functionFile = xmlNode.attrib['file']
      if self.functionFile.endswith('.py') : moduleName = ''.join(self.functionFile.split('.')[:-1]) #remove the .py
      else: moduleName = self.functionFile
      importedModule = utils.importFromPath(moduleName)
      if not importedModule: raise IOError('Failed to import the module '+moduleName+' supposed to contain the function: '+self.name)
      #here the methods in the imported file are brought inside the class
      for method in importedModule.__dict__.keys():
        if method in ['__residuumSign__','__residuumSign','residuumSign',
                      '__supportBoundingTest__','__supportBoundingTest','supportBoundingTest',
                      '__residuum__','__residuum','residuum','__gradient__','__gradient','gradient']:
          if method in ['__residuumSign__','__residuumSign','residuumSign']:
            self.__residuumSign                                =  importedModule.__dict__[method]
            self.__actionDictionary['residuumSign' ]           = self.__residuumSign
            self.__actionImplemented['residuumSign']           = True
          else:self.__actionImplemented['residuumSign']        = False
          if method in ['__supportBoundingTest__','__supportBoundingTest','supportBoundingTest']:
            self.__supportBoundingTest                         =  importedModule.__dict__[method]
            self.__actionDictionary['supportBoundingTest' ]    = self.__supportBoundingTest
            self.__actionImplemented['supportBoundingTest']    = True
          else:self.__actionImplemented['supportBoundingTest'] = False
          if method in ['__residuum__','__residuum','residuum']:
            self.__residuum                                    =  importedModule.__dict__[method]
            self.__actionDictionary['residuum' ]               = self.__residuum
            self.__actionImplemented['residuum']               = True
          else:self.__actionImplemented['residuum']            = False
          if method in ['__gradient__','__gradient','gradient']:
            self.__gradient                                    =  importedModule.__dict__[method]
            self.__actionDictionary['gradient']                = self.__gradient
            self.__actionImplemented['gradient']               = True
          else:self.__actionImplemented['gradient']            = False
        else:
          #custom
          self.__actionDictionary[method]                    = importedModule.__dict__[method]
          self.__actionImplemented[method]                   = True
    else: raise IOError(self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> No file name for the external function has been provided for external function '+self.name+' of type '+self.type)
    cnt = 0
    for child in xmlNode:
      if child.tag=='variable':
        execCommand('self.'+child.text+' = None',self=self)
        self.__inputVariables.append(child.text)
        cnt +=1
        if len(child.attrib.keys()) > 0: raise IOError( self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> variable block in the definition of the function '+self.name + ' should not have any attribute!')
    if cnt == 0: raise IOError( self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> not variable found in the definition of the function '+self.name)

  def addInitParams(self,tempDict):
    '''
    This function is called from the base class to print some of the information inside the class.
    Whatever is permanent in the class and not inherited from the parent class should be mentioned here
    The information is passed back in the dictionary. No information about values that change during the simulation are allowed
    @ In/Out tempDict: {'attribute name':value}
    '''
    tempDict['Module file name'                    ] = self.functionFile
    tempDict['The residuum is provided'            ] = self.__actionImplemented['residuum']
    tempDict['The sign of the residuum is provided'] = self.__actionImplemented['residuumSign']
    tempDict['The gradient is provided'            ] = self.__actionImplemented['gradient']
    tempDict['The support bonding is provided'     ] = self.__actionImplemented['supportBoundingTest']
    for key,value in enumerate(self.__actionImplemented):
      if key not in ['residualSign','supportBoundingTest','residual','gradient']: tempDict['Custom Function'] = value
  def addCurrentSetting(self,tempDict):
    '''
    This function is called from the base class to print some of the information inside the class.
    Whatever is a temporary value in the class and not inherited from the parent class should be mentioned here
    The information is passed back in the dictionary
    Function adds the current settings in a temporary dictionary
    @ In, tempDict
    @ Out, tempDict
    '''
    for key in self.__inputVariables: execCommand("object['variable "+str(key)+" has value']=self."+key,self=self,object=tempDict)

  def __importValues(self,myInput):
    '''this makes available the variable values sent in as self.key'''
    if type(myInput)==dict         :self.__inputFromWhat['dict'](myInput)
    elif 'Data' in myInput.__base__:self.__inputFromWhat['Data'](myInput)
    else: raise Exception (self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> Unknown type of input provided to the function '+str(self.name))

  def __inputFromData(self,inputData):
    '''
    This is meant to be used to collect the input from a Data. A conversion to the declared type of data is attempted by inputData.extractValue'''
    for key in self.__inputVariables:
      #exec('self.'+key+'=inputData.extractValue(myType,key)')
      ##### TEMPORARY FIXXXXXXXX - ALIAS NEEDED#######
      print('FIXME: Alias are already in place why we have still the fixme (once done see also if the loop should contain the myType???')
      foundperfectly = False
      for index in range(len(inputData.dataParameters['inParam'])):
        if key == inputData.dataParameters['inParam'][index]: foundperfectly = True
      if not foundperfectly:
        for index in range(len(inputData.dataParameters['outParam'])):
          if key == inputData.dataParameters['outParam'][index]: foundperfectly = True
      if foundperfectly: execCommand('self.'+key+'=object.extractValue(myType,"'+key+'")',self=self,object=inputData)
      if not foundperfectly:
        semifound = False
        for index in range(len(inputData.dataParameters['inParam'])):
          if key in inputData.dataParameters['inParam'][index]:
            similarVariable = inputData.dataParameters['inParam'][index]
            semifound = True
        if not semifound:
          for index in range(len(inputData.dataParameters['outParam'])):
            if key in inputData.dataParameters['outParam'][index]:
              similarVariable = inputData.dataParameters['outParam'][index]
              semifound = True
        if semifound: execCommand('self.'+key+'=object.extractValue(myType,"'+similarVariable+'")',self=self,object=inputData)

  def __inputFromDict(self,myInputDict):
    '''
    This is meant to be used to collect the input directly from a sampler generated input or simply from a generic dictionary
    In case the input come from a sampler the expected structure is myInputDict['SampledVars'][variable name] = value
    In case it is a generic dictionary the expected structure is myInputDict[variable name] = value
    '''
    if 'SampledVars' in myInputDict.keys(): inDict = myInputDict['SampledVars']
    else                                  : inDict = myInputDict
    for name in self.__inputVariables:
      if name in inDict.keys(): execCommand('self.'+name+'=object["'+name+'"]',self=self,object=inDict)
      else                    : raise Exception(self.printTag+': ' +utils.returnPrintPostTag('ERROR') + '-> The input variable '+name+' in external function seems not to be passed in')

  def evaluate(self,what,myInput):
    '''return the result of the type of action described by 'what' '''
    self.__importValues(myInput)
    toBeReturned=self.__actionDictionary[what](self)
    return toBeReturned

  def evaluateArgs(self,method,args):
    '''Will take a method from a Function object and apply the list of arguments
       defined by args to the method evaluation
    '''
    toBeReturned=self.__actionDictionary[method](args)
    return toBeReturned

  def parameterNames(self,method=None):
    '''
    Just get a list of the variables this function needs, alternatively, you
    can pass in a specific method from this Function and it will give you just
    the arguments that function needs. In this way, an external function would
    not have to reference self, and could just have matching variable names, or
    better yet, the xml could define the aliasing from the external script to
    the internal data
    '''
    if method is None:
      return self.__inputVariables[:]
    else:
    # func_code.co_varnames and func_code.co_argcount are python built-ins that
    # expose the variables and argument count of a python function, this allows
    # us to return the names of the function parameters/arguments
      argCount = self.__actionDictionary[method].func_code.co_argcount
    # So, the first argCount elements of co_varnames will always be the function
    # input argument list
      return self.__actionDictionary[method].func_code.co_varnames[:argCount]

class PythonFunction(Function):
'''
   Written by <NAME> 1/8/15
   This is a generic python function interface for allowing users to use a more
   reusable external python interface. In such an interface, the user decides 
   what variables in their data object map to what variables in their python
   module. In this way, a user could define a python function that does 
   something like normalize a column and could use it on any arbitrary dataset
   without having to modify the script.
'''

  def __init__(self):
    super(self.__class__, self).__init__()
  
  def evaluate(self,method,args):
    '''Will take a method from a Function object and apply the list of arguments
       defined by args to the method evaluation
    '''
  
    # I am accessing the private member from the base class in order to call the
    # external function with the arguments passed in by the user, not 'self'
    # I apologize for not having a better way to access this guy. -DM
    toBeReturned=self._Function__actionDictionary[method](**args)
    return toBeReturned

'''
 Interface Dictionary (factory) (private)
'''

__base = 'function'
__interFaceDict = {}
__interFaceDict['Basic']    = PythonFunction
__interFaceDict['External'] = Function
__knownTypes                = __interFaceDict.keys()


def knonwnTypes():
  return __knownTypes

def returnInstance(Type):
  '''This function return an instance of the request model type'''
  if Type in knonwnTypes():return __interFaceDict[Type]()
  else: raise NameError('not known '+__base+' type '+Type)
{'fraction_non_alphanumeric': 0.12410220181325798, 'fraction_numerical': 0.0012559362612347424, 'mean_word_length': 2.6730575176589304, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4588426', 'n_tokens_mistral': 6342, 'n_tokens_neox': 6159, 'n_words': 2075}