Exemplo n.º 1
0
 def __getitem__(self, arg):
     """ Returns partial function for arg. For Sympy statistical distributions,
 additional returns are supported:
 If set(), then the original distribution is returned.
 If [], then the distribution probability class object is returned.
 If {}, the full expression dictionary is returned.
 If (), the logp or prob expression (depending on pscale) is returned
 If '', then the keys of the partials dictionary are returned.
 Note arg cannot be a slice object.
 """
     if isinstance(arg, slice):
         raise TypeError("Argument input cannot be slice or tuple type")
     if isinstance(arg, (list, dict, set, tuple)):
         assert self.__issympy, \
             "Input argument of type {} supported only Sympy distributions".type(arg)
         assert not len(arg), \
             "Input argument of type {} must be empty".type(arg)
         if isinstance(arg, set):
             return self._distr
         if isinstance(arg, list):
             return self._probj
         if isinstance(arg, dict):
             return self._exprs
         if isinstance(arg, tuple):
             if self._logp:
                 return self._exprs['logp'].expr
             return self._exprs['prob'].expr
     if isinstance(arg, str) and not len(arg):
         return list(self._exprs.keys())
     return Expression.__getitem__(self, arg)
Exemplo n.º 2
0
    def set_ufun(self, ufun=None, *args, **kwds):
        """ Sets a monotonic invertible tranformation for the domain as a tuple of
    two functions in the form (transforming_function, inverse_function) 
    operating on the first argument with optional further args and kwds.

    :param ufun: two-length tuple of monotonic functions.
    :param *args: args to pass to ufun functions.
    :param **kwds: kwds to pass to ufun functions.

    Support for this transformation is only valid for float-type vtypes.
    """
        self._ufun = ufun
        if self._ufun is None:
            return
        self.__no_ucov = False if 'no_ucov' not in kwds else kwds.pop(
            'no_ucov')

        # Non-iconic ufun inputs must a two-length-tuple
        if not isiconic(self._ufun):
            assert self._vtype in VTYPES[float], \
                "Values transformation function only supported for floating point"
            message = "Non-iconic input ufun be a two-sized tuple of callable functions"
            assert isinstance(self._ufun, tuple), message
            assert len(self._ufun) == 2, message
            assert callable(self._ufun[0]), message
            assert callable(self._ufun[1]), message
        elif 'invertible' not in kwds:
            kwds.update({'invertible': True})
        self._ufun = Expression(self._ufun, *args, **kwds)
        if self.__no_ucov:
            assert self._ufun.isiconic, \
                "Can only set no_ucon=True for iconic univariate functions"
        self._eval_ulims()
Exemplo n.º 3
0
    def set_sfun(self, sfun=None, *args, **kwds):
        """ Sets the random variate sampling function with respect to the callable 
    function set by set_prob(). It is necessary to set this functions if 
    sampling variables within infinite limits.

    :param sfun: a callable function or a two-tuple pair of functions
    :param *args: arguments to pass to callable function
    :param **kwds: keywords to pass to callable function

    If two functions are entered, the first is assumed for contiguous 
    unrandomised sampling, and the second for randomised.
    """
        self._sfun = sfun
        if self._sfun is None:
            return

        # Non-iconic sfun inputs must a two-length-tuple
        if not isiconic(self._sfun):
            message = "Input sfun must be a single or pair of callable functions"
            if isinstance(self._sfun, tuple):
                assert len(self._sfun) == 2, message
                assert callable(self._sfun[0]), message
                assert callable(self._sfun[1]), message
            else:
                assert callable(self._sfun), message
        self._sfun = Expression(self._sfun, *args, **kwds)
Exemplo n.º 4
0
  def set_tran(self, tran=None, *args, **kwds):
    """ Sets a transitional function as a conditional probability. This can
    be specified numerically or one or two callable functions.

    :param tran: conditional scalar, array, or callable function (see below).
    :param *args: args to pass to tran functions.
    :param **kwds: kwds to pass to tran functions.

    If tran is a scalar, array, or callable function, then the transitional
    conditionality is treated as symmetrical. If tran is a two-length tuple,
    then assymetry is assumed in the form: (p[var'|var], p[var|var']).

    If intending to sample from a transitional conditional probability density
    function, the corresponding (CDF, ICDF) must be set using set_tfun().
    """
    self._tran = tran
    self.__sym_tran = None
    if self._tran is None:
      return
    self._tran = Expression(self._tran, *args, **kwds)
    self.__sym_tran = not self._tran.ismulti
    if self._tran.callable or self._tran.isscalar:
      return
    assert self._vtype not in VTYPES[float],\
      "Scalar or callable transitional required for floating point data types"
    tran = self._tran() if self.__sym_tran else self._tran[0]()
    message = "Transition matrix must a square 2D Numpy array " + \
              "covering variable set of size {}".format(len(self._vset))
    assert isinstance(tran, np.ndarray), message
    assert tran.ndim == 2, message
    assert np.all(np.array(tran.shape) == len(self._vset)), message
    self.__sym_tran = np.allclose(tran, tran.T)
Exemplo n.º 5
0
    def set_delta(self, delta=None, *args, **kwds):
        """ Sets the default delta operation for the domain.

    :param delta: a callable or uncallable argument (see below)
    :param *args: args to pass if delta is callable.
    :param **kwds: kwds to pass if delta is callable (except scale and bound)

    The first argument delta may be:

    1. A callable function (operating on the first term).
    2. A Variable.Delta instance (this defaults all Variable deltas).
    3. A scalar that may or may not be contained in a container:
      a) No container - the scalar is treated as a fixed delta.
      b) List - delta is uniformly sampled from [-scalar to +scalar].
      c) Tuple - operation is +/-delta within the polarity randomised

    Two reserved keywords can be passed for specifying (default False):
      'scale': Flag to denote scaling deltas to Variable lengths
      'bound': Flag to constrain delta effects to Variable bounds
    """
        self._delta = delta
        self._delta_args = args
        self._delta_kwds = dict(kwds)
        if self._delta is None:
            return
        elif callable(self._delta):
            self._delta = Expression(self._delta, *args, **kwds)
            return

        # Default scale and bound
        if 'scale' not in self._delta_kwds:
            self._delta_kwds.update({'scale': False})
        if 'bound' not in self._delta_kwds:
            self._delta_kwds.update({'bound': False})
Exemplo n.º 6
0
 def set_update(self, update=None, *args, **kwds):
   self._update = update
   if self._update is None:
     return
   if self._update in MCMC_SAMPLERS:
     assert not args and not kwds, \
         "Neither args nor kwds permitted with spec '{}'".format(self._update)
     self.set_update(MCMC_SAMPLERS[self._update][2])
     return
   self._update = Expression(self._update, *args, **kwds)
Exemplo n.º 7
0
 def set_thresh(self, thresh=None, *args, **kwds):
   self._thresh = thresh
   if self._thresh is None:
     return
   if self._thresh in MCMC_SAMPLERS:
     assert not args and not kwds, \
         "Neither args nor kwds permitted with spec '{}'".format(self._thresh)
     self.set_thresh(MCMC_SAMPLERS[self._thresh][1])
     self.set_update(thresh)
     return
   self._thresh = Expression(self._thresh, *args, **kwds)
Exemplo n.º 8
0
 def set_scores(self, scores=None, *args, **kwds):
   self._scores = scores
   if self._scores is None:
     return
   if self._scores in MCMC_SAMPLERS:
     assert not args and not kwds, \
         "Neither args nor kwds permitted with spec '{}'".format(self._scores)
     self.set_scores(MCMC_SAMPLERS[self._scores][0], pscale=self._pscale)
     self.set_thresh(scores)
     return
   self._scores = Expression(self._scores, *args, **kwds)
Exemplo n.º 9
0
  def set_tfun(self, tfun=None, *args, **kwds):
    """ Sets a two-length tuple of functions that should correspond to the
    (cumulative probability function, inverse cumulative function) with respect
    to the callable function set by set_tran(). It is necessary to set these
    functions if conditionally sampling variables with continuous distributions.

    :param tfun: two-length tuple of callable functions
    :param *args: arguments to pass to tfun functions
    :param **kwds: keywords to pass to tfun functions
    """
    self._tfun = tfun if tfun is None else Expression(tfun, *args, **kwds)
    if self._tfun is None:
      return
    assert self._tfun.ismulti, "Tuple of two functions required"
Exemplo n.º 10
0
    def set_prop(self, prop=None, *args, **kwds):
        """ Sets the joint proposition function with optional arguments and keywords.

    :param prop: may be a scalar, array, or callable function.
    :param *args: optional arguments to pass if prop is callable.
    :param **kwds: optional keywords to pass if prop is callable.
    """
        self._prop = prop
        self._prop_deps = self._keylist if 'deps' not in kwds else kwds.pop[
            'deps']
        if self._prop is None:
            return
        assert self._anystoch, \
            "Cannot set field proposition without any random variables"
        assert self._tran is None, \
            "Cannot assign both proposition and transition probabilities"
        self._prop = Expression(self._prop, *args, **kwds)
Exemplo n.º 11
0
    def set_pfun(self, pfun=None, *args, **kwds):
        """ Sets a two-length tuple of functions that should correspond to the
    (cumulative probability function, inverse cumulative function) with respect
    to the callable function set by set_prob(). It is necessary to set these
    functions if sampling variables non-randomly with non-flat distributions.

    :param pfun: two-length tuple of callable functions
    :param *args: arguments to pass to callable function
    :param **kwds: keywords to pass to callable function
    """
        self._pfun = pfun
        if self._pfun is None:
            return

        # Non-iconic ufun inputs must a two-length-tuple
        if not isiconic(self._pfun):
            message = "Input pfun must be a two-sized tuple of callable functions"
            assert isinstance(self._pfun, tuple), message
            assert len(self._pfun) == 2, message
            assert callable(self._pfun[0]), message
            assert callable(self._pfun[1]), message
        elif 'invertible' not in kwds:
            kwds.update({'invertible': True})
        self._pfun = Expression(self._pfun, *args, **kwds)
Exemplo n.º 12
0
    def set_delta(self, delta=None, *args, **kwds):
        """ Sets the default delta function or operation.

    :param delta: the delta function or operation (see below)
    :param *args: optional arguments to pass if delta is callable.
    :param **kwds: optional keywords to pass if delta is callable.

    The input delta may be:

    1. A callable function (for which args and kwds are passed on as usual).
    2. An Variable.Delta instance (this defaults all Variable Deltas).
    3. A dictionary for Variables, this is converted to an Field.Delta.
    4. A scalar that may contained in a list or tuple:
      a) No container - the scalar is treated as a fixed delta.
      b) List - delta is uniformly and independently sampled across Variabless.
      c) Tuple - delta is spherically sampled across Variables.

      For non-tuples, an optional argument (args[0]) can be included as a 
      dictionary to specify by Variable-name deltas following the above 
      conventions except their values are not subject to scaling even if 
      'scale' is given, but they are subject to bounding if 'bound' is 
      specified.

    For setting types 2-4, optional keywords are (default False):
      'scale': Flag to denote scaling deltas to Variable lengths
      'bound': Flag to constrain delta effects to Variable bounds (None bounces)
      
    """
        self._delta = delta
        self._delta_args = args
        self._delta_kwds = dict(kwds)
        self._spherise = {}
        if self._delta is None:
            return
        elif callable(self._delta):
            self._delta = Expression(self._delta, *args, **kwds)
            return

        # Default scale and bound
        if 'scale' not in self._delta_kwds:
            self._delta_kwds.update({'scale': False})
        if 'bound' not in self._delta_kwds:
            self._delta_kwds.update({'bound': False})
        scale = self._delta_kwds['scale']
        bound = self._delta_kwds['bound']

        # Handle deltas and dictionaries
        if isinstance(self._delta, dict):
            self._delta = self._delta_type(**self._delta)
        if isinstance(delta, self._delta_type):
            assert not args, \
              "Optional args prohibited for dict/delta instance inputs"
            for i, var in enumerate(self._varlist):
                var.set_delta(self._delta[i], scale=scale, bound=bound)
            return

        # Default scale and bound and check args
        if self._delta_args:
            assert len(self._delta_args) == 1, \
                "Optional positional arguments must comprises a single dict"
            unscale = self._delta_args[0]
            assert isinstance(unscale, dict), \
                "Optional positional arguments must comprises a single dict"

        # Non tuples can be converted to deltas; can pre-scale here
        if not isinstance(self._delta, tuple):
            delta = self._delta
            urand = isinstance(delta, list)
            if urand:
                assert len(delta) == 1, "List delta requires a single element"
                delta = delta[0]
            deltas = {key: delta for key in self._keylist}
            unscale = {} if not self._delta_args else self._delta_args[0]
            deltas.update(unscale)
            delta_dict = collections.OrderedDict(deltas)
            for i, (key, val) in enumerate(deltas.items()):
                delta = val
                if scale and key not in unscale:
                    assert np.isfinite(self._lengths[i]), \
                        "Cannot scale by infinite length for Variable {}".format(key)
                    delta = val * self._lengths[i]
                if urand:
                    delta = [delta]
                delta_dict.update({key: delta})
            self._delta = self._delta_type(**delta_dict)
            for i, var in enumerate(self._varlist):
                var.set_delta(self._delta[i], scale=False, bound=bound)

        # Tuple deltas must be evaluated on-the-fly and cannot be pre-scaled
        else:
            unscale = {} if not self._delta_args else self._delta_args[0]
            self._spherise = {}
            for i, key in enumerate(self._keylist):
                if key not in unscale.keys():
                    length = self._lengths[i]
                    assert np.isfinite(length), \
                        "Cannot spherise Variable {} with infinite length".format(key)
                    self._spherise.update({key: length})
Exemplo n.º 13
0
    def add_func(self, spec, func, *args, **kwds):
        """ Adds an function dependency for a specificied inp/out relationship where
    func, *args, **kwds are the inputs to the corresponding Expression instance
    (see Expression) and spec specifies the dependent variable(s) (for iconic
    expressions) or (for non-iconic) both dependent and variables.
    """

        # Initialise deps and funcs if not set
        if self._deps is None:
            self._deps = collections.OrderedDict()
        if self._funcs is None:
            self._funcs = collections.OrderedDict()

        # Update deps
        spec_out = None
        spec_inp = None
        if isinstance(spec, dict):
            spec_out = parse_identifiers(tuple(spec.keys()))
            spec_inp = parse_identifiers(tuple(spec.values()))
        else:
            spec_out = parse_identifiers(spec)

        if self._deps:
            assert spec_out not in self._deps.keys(), \
                "Output dependence for {} already previously set".format(spec_out)
        self._deps.update({spec_out: spec_inp})
        self._ndeps = len(self._deps)

        # Update funcs and set isiconic flag if not previously set
        self._funcs.update({spec_out: Expression(func, *args, **kwds)})
        if self._isiconic is None:
            self._isiconic = self._funcs[spec_out].isiconic
        else:
            assert self._isiconic == self._funcs[spec_out].isiconic, \
                "Cannot mix iconic and non-iconic expressions within functional"

        # If iconic, check for single spec_out and update dexpr if available
        if self._isiconic:
            assert not len(args) and not len(kwds), \
                "No optional arguments or keywords supported for iconic functions"
            assert len(spec_out) == 1, \
                "Only single output functions supported for iconic expressions"
            key = list(spec_out)[0]
            assert key in self._out_set, \
                "Key {} not found in amount output set {}".format(key, self._out_set)
            var = self._out_dict[key]
            if hasattr(var, 'dexpr'):
                var.dexpr = func

        # Detect inputs for iconics if not specified
        if spec_inp is None:
            assert self._isiconic, \
                "Input specification mandatory for non-iconic functionals"
            spec_inp = tuple(self._funcs[spec_out].symbols.keys())
            spec_inp = parse_identifiers(spec_inp)
            self._deps[spec_out] = spec_inp

        # Detect subsets are covered
        assert spec_out.issubset(self._out_set), \
            "Specification output must be a subset of output graph"
        assert spec_inp.issubset(self._inp_set), \
            "Specification input must be a subset of input graph"

        self._set_partials()