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)
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()
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)
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)
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})
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)
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)
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)
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"
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)
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)
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})
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()