fpex0.InitialDistribution

  1import numpy as np
  2import sympy 
  3import numbers
  4
  5
  6class InitialDistribution:
  7   """Class holding initial distribution.
  8
  9   ## Parameters
 10
 11   **functionExpressionList** : list
 12   <br> list of piecewise function definitions
 13
 14   **supportList** : list of (tuples or functions)
 15   <br> List of (open) support intervals, use numpy.inf and -numpy.inf for positive/negative infinity
 16   if a function is given, it will be evaluated with current parameters and must return a tuple for the support.
 17
 18   **psymbols** : tuple of sympy symbols
 19   <br> Tuple of parameter set (used in all support intervals).
 20
 21   **xsymbol** : sympy symbol
 22   <br> Symbol used as x variable (default: x).
 23         
 24   **name** : str
 25   <br> A handy name for the distribution.
 26   """
 27
 28   def __init__(self, funcExprList, supportList, psymbols, xsymbol = sympy.symbols("x"), name = "unnamed"):
 29 
 30      # ensure the list variables are indeed lists (wrap singletons)
 31      if not(isinstance(funcExprList, list)):  funcExprList = [funcExprList]
 32      if not(isinstance( supportList, list)):   supportList = [supportList]
 33      
 34      # ensure we have as many functions as supports
 35      assert( len(funcExprList) == len(supportList) )
 36
 37      # dimensions
 38      supportCount  = len(supportList)
 39      paramCount    = len(psymbols)
 40
 41      # generate functions and derivatives w.r.t. p (i.e. dfdp)
 42      dfdp_sym = np.empty( (supportCount,paramCount) , dtype=object )
 43      dfdp_fun = np.empty( (supportCount,paramCount) , dtype=object )
 44      f_sym    = np.empty( supportCount              , dtype=object )
 45      f_fun    = np.empty( supportCount              , dtype=object )
 46      for (j , fExpr) in enumerate(funcExprList):
 47         f_sym[j] = funcExprList[j]                                 # store symbolic function  
 48         f_fun[j] = self.sympy2numpyXP(f_sym[j], xsymbol, psymbols) # transform to function
 49         for (i, p) in enumerate(psymbols):
 50            symfun = sympy.diff(fExpr, p)                                 # derivative w.r.t. p_i
 51            dfdp_sym[j,i] = symfun                                        # store symbolic function
 52            dfdp_fun[j,i] = self.sympy2numpyXP(symfun, xsymbol, psymbols) # transform to function
 53
 54      # transform the support into support functions
 55      for (j, support) in enumerate(supportList):
 56         (l,r) = support               # extract left and right
 57         lfun = self.functionalizeSupportDescriptor(l, psymbols)
 58         rfun = self.functionalizeSupportDescriptor(r, psymbols)
 59         supportList[j] = (lfun,rfun)  # store support functions
 60
 61
 62      # store quantities (mostly for debugging)
 63      self.name     = name
 64      self.funcExprList  = funcExprList
 65      self.supportList   = supportList
 66      self.paramList     = psymbols
 67      self.xsymbol       = xsymbol
 68      self.supportCount  = supportCount
 69      self.paramCount    = paramCount
 70      self.f_fun    = f_fun
 71      self.f_sym    = f_sym
 72      self.dfdp_fun = dfdp_fun
 73      self.dfdp_sym = dfdp_sym
 74
 75      # generate evaluator functions
 76      return None
 77
 78
 79   # some helpers 
 80
 81   @staticmethod
 82   def getValuesWithinInterval(x, interval):
 83      """Returns values and indices of numpy array x that are inside given interval."""
 84      # NOTE: Boolean vs. integer indexing depends on size and count
 85      (l,r) = interval                # extract interval boundaries
 86      idx = np.where((x>l) & (x<r))   # returns a 1-element tuple 
 87      values = x[idx]                 # extract values
 88      return values, idx
 89
 90
 91   @staticmethod
 92   def evalSupport(support, p):
 93      """Evaluates support functions at given parameter vector."""
 94      (l,r) = support
 95      return (l(p), r(p))
 96
 97   @staticmethod
 98   def sympy2numpyXP(fun, xsymbol, psymbols):
 99      """Lambdify specified function as a function of x and p."""
100      xpsymbols = (xsymbol,) + tuple(psymbols)                # collect all symbols
101      multiargfun = sympy.lambdify(xpsymbols, fun, 'numpy')   # function of x and individual parameters
102      xpfun = lambda x,p : multiargfun(x, *p)                 # function that decollates vector p in multiple args
103      return xpfun
104
105
106   @staticmethod
107   def sympy2numpyP(fun, symbols):
108      """Lambdify specified function."""
109      multiargfun = sympy.lambdify(tuple(symbols), fun, 'numpy') # function of individual parameters
110      pfun = lambda p : multiargfun(*p)                          # function that decollated vector p in multiple args
111      return pfun
112
113
114   @staticmethod
115   def functionalizeSupportDescriptor(thing, symbols):
116      """Transforms a support descriptor into a callable function."""
117      fun = None
118      if isinstance(thing, sympy.Expr):      fun = InitialDistribution.sympy2numpyP(thing, symbols)
119      if isinstance(thing, numbers.Number):  fun = lambda *ignore: thing
120      if (fun == None): raise Exception("Invalid support descriptor", thing)
121      return fun
122
123   # @staticmethod
124   # def compileFunction(fun):
125   #    debugdir = "./codegen"
126   #    cfun = autowrap(fun, tempdir=debugdir)
127   #    return cfun
128
129
130   def f(self, x, p):
131      """Evaluates nominal function at x with parameter vector p.
132      ## Takes
133      **x**: numpy vector <br>
134      **p**: numpy vector
135      
136      
137      ## Returns:
138      **f**: numpy vector 
139      """
140      fvals = np.zeros_like(x)
141      for (j,support) in enumerate( self.supportList ):          # walk through the individual functions
142         support = self.evalSupport(support, p)                  #   evaluate support functions
143         (xx, idx) = self.getValuesWithinInterval( x , support ) #   get x-values in current support
144         fvals[idx] = self.f_fun[j]( xx , p )                    #   evaluate function
145      return fvals
146
147
148   def dfdp(self, x, p):
149      """Evaluates jacobian at x with parameter vector p.
150      ## Takes
151      **x**: numpy vector <br> 
152      **p**: numpy vector
153
154
155      ## Returns
156      **J**: Numpy matrix dfdp, J[i][...] contains sensitivity w.r.t. p[i].
157      """
158      dfdpvals = np.zeros_like( x , shape = (len(p),len(x)) )        # preallocate
159      for (j,support) in enumerate( self.supportList ):              # walk through supports
160         support = self.evalSupport(support, p)                      #   determine support
161         (x, idx) =  self.getValuesWithinInterval( x , support )     #   extract x values within support
162         for i in range(0, self.paramCount):                         #   walk through derivative funcions
163            dfdpvals[i][idx] = self.dfdp_fun[j,i](x,p)               #     evaluate derivative functions
164      return dfdpvals
class InitialDistribution:
  7class InitialDistribution:
  8   """Class holding initial distribution.
  9
 10   ## Parameters
 11
 12   **functionExpressionList** : list
 13   <br> list of piecewise function definitions
 14
 15   **supportList** : list of (tuples or functions)
 16   <br> List of (open) support intervals, use numpy.inf and -numpy.inf for positive/negative infinity
 17   if a function is given, it will be evaluated with current parameters and must return a tuple for the support.
 18
 19   **psymbols** : tuple of sympy symbols
 20   <br> Tuple of parameter set (used in all support intervals).
 21
 22   **xsymbol** : sympy symbol
 23   <br> Symbol used as x variable (default: x).
 24         
 25   **name** : str
 26   <br> A handy name for the distribution.
 27   """
 28
 29   def __init__(self, funcExprList, supportList, psymbols, xsymbol = sympy.symbols("x"), name = "unnamed"):
 30 
 31      # ensure the list variables are indeed lists (wrap singletons)
 32      if not(isinstance(funcExprList, list)):  funcExprList = [funcExprList]
 33      if not(isinstance( supportList, list)):   supportList = [supportList]
 34      
 35      # ensure we have as many functions as supports
 36      assert( len(funcExprList) == len(supportList) )
 37
 38      # dimensions
 39      supportCount  = len(supportList)
 40      paramCount    = len(psymbols)
 41
 42      # generate functions and derivatives w.r.t. p (i.e. dfdp)
 43      dfdp_sym = np.empty( (supportCount,paramCount) , dtype=object )
 44      dfdp_fun = np.empty( (supportCount,paramCount) , dtype=object )
 45      f_sym    = np.empty( supportCount              , dtype=object )
 46      f_fun    = np.empty( supportCount              , dtype=object )
 47      for (j , fExpr) in enumerate(funcExprList):
 48         f_sym[j] = funcExprList[j]                                 # store symbolic function  
 49         f_fun[j] = self.sympy2numpyXP(f_sym[j], xsymbol, psymbols) # transform to function
 50         for (i, p) in enumerate(psymbols):
 51            symfun = sympy.diff(fExpr, p)                                 # derivative w.r.t. p_i
 52            dfdp_sym[j,i] = symfun                                        # store symbolic function
 53            dfdp_fun[j,i] = self.sympy2numpyXP(symfun, xsymbol, psymbols) # transform to function
 54
 55      # transform the support into support functions
 56      for (j, support) in enumerate(supportList):
 57         (l,r) = support               # extract left and right
 58         lfun = self.functionalizeSupportDescriptor(l, psymbols)
 59         rfun = self.functionalizeSupportDescriptor(r, psymbols)
 60         supportList[j] = (lfun,rfun)  # store support functions
 61
 62
 63      # store quantities (mostly for debugging)
 64      self.name     = name
 65      self.funcExprList  = funcExprList
 66      self.supportList   = supportList
 67      self.paramList     = psymbols
 68      self.xsymbol       = xsymbol
 69      self.supportCount  = supportCount
 70      self.paramCount    = paramCount
 71      self.f_fun    = f_fun
 72      self.f_sym    = f_sym
 73      self.dfdp_fun = dfdp_fun
 74      self.dfdp_sym = dfdp_sym
 75
 76      # generate evaluator functions
 77      return None
 78
 79
 80   # some helpers 
 81
 82   @staticmethod
 83   def getValuesWithinInterval(x, interval):
 84      """Returns values and indices of numpy array x that are inside given interval."""
 85      # NOTE: Boolean vs. integer indexing depends on size and count
 86      (l,r) = interval                # extract interval boundaries
 87      idx = np.where((x>l) & (x<r))   # returns a 1-element tuple 
 88      values = x[idx]                 # extract values
 89      return values, idx
 90
 91
 92   @staticmethod
 93   def evalSupport(support, p):
 94      """Evaluates support functions at given parameter vector."""
 95      (l,r) = support
 96      return (l(p), r(p))
 97
 98   @staticmethod
 99   def sympy2numpyXP(fun, xsymbol, psymbols):
100      """Lambdify specified function as a function of x and p."""
101      xpsymbols = (xsymbol,) + tuple(psymbols)                # collect all symbols
102      multiargfun = sympy.lambdify(xpsymbols, fun, 'numpy')   # function of x and individual parameters
103      xpfun = lambda x,p : multiargfun(x, *p)                 # function that decollates vector p in multiple args
104      return xpfun
105
106
107   @staticmethod
108   def sympy2numpyP(fun, symbols):
109      """Lambdify specified function."""
110      multiargfun = sympy.lambdify(tuple(symbols), fun, 'numpy') # function of individual parameters
111      pfun = lambda p : multiargfun(*p)                          # function that decollated vector p in multiple args
112      return pfun
113
114
115   @staticmethod
116   def functionalizeSupportDescriptor(thing, symbols):
117      """Transforms a support descriptor into a callable function."""
118      fun = None
119      if isinstance(thing, sympy.Expr):      fun = InitialDistribution.sympy2numpyP(thing, symbols)
120      if isinstance(thing, numbers.Number):  fun = lambda *ignore: thing
121      if (fun == None): raise Exception("Invalid support descriptor", thing)
122      return fun
123
124   # @staticmethod
125   # def compileFunction(fun):
126   #    debugdir = "./codegen"
127   #    cfun = autowrap(fun, tempdir=debugdir)
128   #    return cfun
129
130
131   def f(self, x, p):
132      """Evaluates nominal function at x with parameter vector p.
133      ## Takes
134      **x**: numpy vector <br>
135      **p**: numpy vector
136      
137      
138      ## Returns:
139      **f**: numpy vector 
140      """
141      fvals = np.zeros_like(x)
142      for (j,support) in enumerate( self.supportList ):          # walk through the individual functions
143         support = self.evalSupport(support, p)                  #   evaluate support functions
144         (xx, idx) = self.getValuesWithinInterval( x , support ) #   get x-values in current support
145         fvals[idx] = self.f_fun[j]( xx , p )                    #   evaluate function
146      return fvals
147
148
149   def dfdp(self, x, p):
150      """Evaluates jacobian at x with parameter vector p.
151      ## Takes
152      **x**: numpy vector <br> 
153      **p**: numpy vector
154
155
156      ## Returns
157      **J**: Numpy matrix dfdp, J[i][...] contains sensitivity w.r.t. p[i].
158      """
159      dfdpvals = np.zeros_like( x , shape = (len(p),len(x)) )        # preallocate
160      for (j,support) in enumerate( self.supportList ):              # walk through supports
161         support = self.evalSupport(support, p)                      #   determine support
162         (x, idx) =  self.getValuesWithinInterval( x , support )     #   extract x values within support
163         for i in range(0, self.paramCount):                         #   walk through derivative funcions
164            dfdpvals[i][idx] = self.dfdp_fun[j,i](x,p)               #     evaluate derivative functions
165      return dfdpvals

Class holding initial distribution.

Parameters

functionExpressionList : list
list of piecewise function definitions

supportList : list of (tuples or functions)
List of (open) support intervals, use numpy.inf and -numpy.inf for positive/negative infinity if a function is given, it will be evaluated with current parameters and must return a tuple for the support.

psymbols : tuple of sympy symbols
Tuple of parameter set (used in all support intervals).

xsymbol : sympy symbol
Symbol used as x variable (default: x).

name : str
A handy name for the distribution.

InitialDistribution(funcExprList, supportList, psymbols, xsymbol=x, name='unnamed')
29   def __init__(self, funcExprList, supportList, psymbols, xsymbol = sympy.symbols("x"), name = "unnamed"):
30 
31      # ensure the list variables are indeed lists (wrap singletons)
32      if not(isinstance(funcExprList, list)):  funcExprList = [funcExprList]
33      if not(isinstance( supportList, list)):   supportList = [supportList]
34      
35      # ensure we have as many functions as supports
36      assert( len(funcExprList) == len(supportList) )
37
38      # dimensions
39      supportCount  = len(supportList)
40      paramCount    = len(psymbols)
41
42      # generate functions and derivatives w.r.t. p (i.e. dfdp)
43      dfdp_sym = np.empty( (supportCount,paramCount) , dtype=object )
44      dfdp_fun = np.empty( (supportCount,paramCount) , dtype=object )
45      f_sym    = np.empty( supportCount              , dtype=object )
46      f_fun    = np.empty( supportCount              , dtype=object )
47      for (j , fExpr) in enumerate(funcExprList):
48         f_sym[j] = funcExprList[j]                                 # store symbolic function  
49         f_fun[j] = self.sympy2numpyXP(f_sym[j], xsymbol, psymbols) # transform to function
50         for (i, p) in enumerate(psymbols):
51            symfun = sympy.diff(fExpr, p)                                 # derivative w.r.t. p_i
52            dfdp_sym[j,i] = symfun                                        # store symbolic function
53            dfdp_fun[j,i] = self.sympy2numpyXP(symfun, xsymbol, psymbols) # transform to function
54
55      # transform the support into support functions
56      for (j, support) in enumerate(supportList):
57         (l,r) = support               # extract left and right
58         lfun = self.functionalizeSupportDescriptor(l, psymbols)
59         rfun = self.functionalizeSupportDescriptor(r, psymbols)
60         supportList[j] = (lfun,rfun)  # store support functions
61
62
63      # store quantities (mostly for debugging)
64      self.name     = name
65      self.funcExprList  = funcExprList
66      self.supportList   = supportList
67      self.paramList     = psymbols
68      self.xsymbol       = xsymbol
69      self.supportCount  = supportCount
70      self.paramCount    = paramCount
71      self.f_fun    = f_fun
72      self.f_sym    = f_sym
73      self.dfdp_fun = dfdp_fun
74      self.dfdp_sym = dfdp_sym
75
76      # generate evaluator functions
77      return None
@staticmethod
def getValuesWithinInterval(x, interval):
82   @staticmethod
83   def getValuesWithinInterval(x, interval):
84      """Returns values and indices of numpy array x that are inside given interval."""
85      # NOTE: Boolean vs. integer indexing depends on size and count
86      (l,r) = interval                # extract interval boundaries
87      idx = np.where((x>l) & (x<r))   # returns a 1-element tuple 
88      values = x[idx]                 # extract values
89      return values, idx

Returns values and indices of numpy array x that are inside given interval.

@staticmethod
def evalSupport(support, p):
92   @staticmethod
93   def evalSupport(support, p):
94      """Evaluates support functions at given parameter vector."""
95      (l,r) = support
96      return (l(p), r(p))

Evaluates support functions at given parameter vector.

@staticmethod
def sympy2numpyXP(fun, xsymbol, psymbols):
 98   @staticmethod
 99   def sympy2numpyXP(fun, xsymbol, psymbols):
100      """Lambdify specified function as a function of x and p."""
101      xpsymbols = (xsymbol,) + tuple(psymbols)                # collect all symbols
102      multiargfun = sympy.lambdify(xpsymbols, fun, 'numpy')   # function of x and individual parameters
103      xpfun = lambda x,p : multiargfun(x, *p)                 # function that decollates vector p in multiple args
104      return xpfun

Lambdify specified function as a function of x and p.

@staticmethod
def sympy2numpyP(fun, symbols):
107   @staticmethod
108   def sympy2numpyP(fun, symbols):
109      """Lambdify specified function."""
110      multiargfun = sympy.lambdify(tuple(symbols), fun, 'numpy') # function of individual parameters
111      pfun = lambda p : multiargfun(*p)                          # function that decollated vector p in multiple args
112      return pfun

Lambdify specified function.

@staticmethod
def functionalizeSupportDescriptor(thing, symbols):
115   @staticmethod
116   def functionalizeSupportDescriptor(thing, symbols):
117      """Transforms a support descriptor into a callable function."""
118      fun = None
119      if isinstance(thing, sympy.Expr):      fun = InitialDistribution.sympy2numpyP(thing, symbols)
120      if isinstance(thing, numbers.Number):  fun = lambda *ignore: thing
121      if (fun == None): raise Exception("Invalid support descriptor", thing)
122      return fun

Transforms a support descriptor into a callable function.

def f(self, x, p):
131   def f(self, x, p):
132      """Evaluates nominal function at x with parameter vector p.
133      ## Takes
134      **x**: numpy vector <br>
135      **p**: numpy vector
136      
137      
138      ## Returns:
139      **f**: numpy vector 
140      """
141      fvals = np.zeros_like(x)
142      for (j,support) in enumerate( self.supportList ):          # walk through the individual functions
143         support = self.evalSupport(support, p)                  #   evaluate support functions
144         (xx, idx) = self.getValuesWithinInterval( x , support ) #   get x-values in current support
145         fvals[idx] = self.f_fun[j]( xx , p )                    #   evaluate function
146      return fvals

Evaluates nominal function at x with parameter vector p.

Takes

x: numpy vector
p: numpy vector

Returns:

f: numpy vector

def dfdp(self, x, p):
149   def dfdp(self, x, p):
150      """Evaluates jacobian at x with parameter vector p.
151      ## Takes
152      **x**: numpy vector <br> 
153      **p**: numpy vector
154
155
156      ## Returns
157      **J**: Numpy matrix dfdp, J[i][...] contains sensitivity w.r.t. p[i].
158      """
159      dfdpvals = np.zeros_like( x , shape = (len(p),len(x)) )        # preallocate
160      for (j,support) in enumerate( self.supportList ):              # walk through supports
161         support = self.evalSupport(support, p)                      #   determine support
162         (x, idx) =  self.getValuesWithinInterval( x , support )     #   extract x values within support
163         for i in range(0, self.paramCount):                         #   walk through derivative funcions
164            dfdpvals[i][idx] = self.dfdp_fun[j,i](x,p)               #     evaluate derivative functions
165      return dfdpvals

Evaluates jacobian at x with parameter vector p.

Takes

x: numpy vector
p: numpy vector

Returns

J: Numpy matrix dfdp, J[i][...] contains sensitivity w.r.t. p[i].