def _rvindexed_subs(self, expr, condition=None): """ Substitutes the RandomIndexedSymbol with the RandomSymbol with same name, distribution and probability as RandomIndexedSymbol. """ rvs_expr = random_symbols(expr) if len(rvs_expr) != 0: swapdict_expr = {} for rv in rvs_expr: if isinstance(rv, RandomIndexedSymbol): newrv = Bernoulli(rv.name, p=rv.pspace.process.p, succ=self.success, fail=self.failure) swapdict_expr[rv] = newrv expr = expr.subs(swapdict_expr) rvs_cond = random_symbols(condition) if len(rvs_cond) != 0: swapdict_cond = {} if condition is not None: for rv in rvs_cond: if isinstance(rv, RandomIndexedSymbol): newrv = Bernoulli(rv.name, p=rv.pspace.process.p, succ=self.success, fail=self.failure) swapdict_cond[rv] = newrv condition = condition.subs(swapdict_cond) return expr, condition
def set(self): rvs = [i for i in random_symbols(self.args[1])] marginalise_out = [i for i in random_symbols(self.args[1]) \ if i not in self.args[1]] for i in rvs: if i in marginalise_out: rvs.remove(i) return ProductSet((i.pspace.set for i in rvs))
def pdf(self, x): dist = self.args[0] randoms = [] for arg in dist.args: randoms.extend(random_symbols(arg)) if len(randoms) > 1: raise NotImplementedError("Compound Distributions for more than" " one random argument is not implemeted yet.") rand_sym = randoms[0] if isinstance(dist, SingleFiniteDistribution): y = Dummy('y', integer=True, negative=False) _pdf = dist.pmf(y) else: y = Dummy('y') _pdf = dist.pdf(y) if isinstance(rand_sym.pspace.distribution, SingleFiniteDistribution): rand_dens = rand_sym.pspace.distribution.pmf(rand_sym) else: rand_dens = rand_sym.pspace.distribution.pdf(rand_sym) rand_sym_dom = rand_sym.pspace.domain.set if rand_sym.pspace.is_Discrete or rand_sym.pspace.is_Finite: _pdf = Sum(_pdf*rand_dens, (rand_sym, rand_sym_dom._inf, rand_sym_dom._sup)).doit() else: _pdf = integrate(_pdf*rand_dens, (rand_sym, rand_sym_dom._inf, rand_sym_dom._sup)) return Lambda(y, _pdf)(x)
def doit(self, **hints): condition = self.args[0] given_condition = self._condition numsamples = hints.get('numsamples', False) for_rewrite = not hints.get('for_rewrite', False) if isinstance(condition, Not): return S.One - self.func(condition.args[0], given_condition, evaluate=for_rewrite).doit(**hints) if condition.has(RandomIndexedSymbol): return pspace(condition).probability(condition, given_condition, evaluate=for_rewrite) if isinstance(given_condition, RandomSymbol): condrv = random_symbols(condition) if len(condrv) == 1 and condrv[0] == given_condition: from sympy.stats.frv_types import BernoulliDistribution return BernoulliDistribution( self.func(condition).doit(**hints), 0, 1) if any([dependent(rv, given_condition) for rv in condrv]): return Probability(condition, given_condition) else: return Probability(condition).doit() if given_condition is not None and \ not isinstance(given_condition, (Relational, Boolean)): raise ValueError( "%s is not a relational or combination of relationals" % (given_condition)) if given_condition == False or condition is S.false: return S.Zero if not isinstance(condition, (Relational, Boolean)): raise ValueError( "%s is not a relational or combination of relationals" % (condition)) if condition is S.true: return S.One if numsamples: return sampling_P(condition, given_condition, numsamples=numsamples) if given_condition is not None: # If there is a condition # Recompute on new conditional expr return Probability(given(condition, given_condition)).doit() # Otherwise pass work off to the ProbabilitySpace if pspace(condition) == PSpace(): return Probability(condition, given_condition) result = pspace(condition).probability(condition) if hasattr(result, 'doit') and for_rewrite: return result.doit() else: return result
def where(self, condition): rvs = frozenset(random_symbols(condition)) if not (len(rvs) == 1 and rvs.issubset(self.values)): raise NotImplementedError("Multiple continuous random variables not supported") rv = tuple(rvs)[0] interval = reduce_rational_inequalities_wrap(condition, rv) interval = interval.intersect(self.domain.set) return SingleContinuousDomain(rv.symbol, interval)
def probability(self, condition, given_condition=None, evaluate=True, **kwargs): """ Computes probability. Parameters ========== condition: Relational Condition for which probability has to be computed. Must contain a RandomIndexedSymbol of the process. given_condition: Relational/And The given conditions under which computations should be done. Returns ======= Probability of the condition. """ new_condition, new_givencondition = self._rvindexed_subs( condition, given_condition) if isinstance(new_givencondition, RandomSymbol): condrv = random_symbols(new_condition) if len(condrv) == 1 and condrv[0] == new_givencondition: return BernoulliDistribution(self.probability(new_condition), 0, 1) if any([dependent(rv, new_givencondition) for rv in condrv]): return Probability(new_condition, new_givencondition) else: return self.probability(new_condition) if new_givencondition is not None and \ not isinstance(new_givencondition, (Relational, Boolean)): raise ValueError( "%s is not a relational or combination of relationals" % (new_givencondition)) if new_givencondition == False: return S.Zero if new_condition == True: return S.One if new_condition == False: return S.Zero if not isinstance(new_condition, (Relational, Boolean)): raise ValueError( "%s is not a relational or combination of relationals" % (new_condition)) if new_givencondition is not None: # If there is a condition # Recompute on new conditional expr return self.probability( given(new_condition, new_givencondition, **kwargs), **kwargs) return pspace(new_condition).probability(new_condition, **kwargs)
def __new__(cls,dist, rvs): if not all([isinstance(rv, (Indexed, RandomSymbol))] for rv in rvs): raise ValueError(filldedent('''Marginal distribution can be intitialised only in terms of random variables or indexed random variables''')) rvs = Tuple.fromiter(rv for rv in rvs) if not isinstance(dist, JointDistribution) and len(random_symbols(dist)) == 0: return dist return Basic.__new__(cls, dist, rvs)
def where(self, condition): rvs = frozenset(random_symbols(condition)) if not (len(rvs) == 1 and rvs.issubset(self.values)): raise NotImplementedError( "Multiple continuous random variables not supported") rv = tuple(rvs)[0] interval = reduce_poly_inequalities_wrap(condition, rv) interval = interval.intersect(self.domain.set) return SingleContinuousDomain(rv.symbol, interval)
def where(self, condition): rvs = random_symbols(condition) assert all(r.symbol in self.symbols for r in rvs) if (len(rvs) > 1): raise NotImplementedError(filldedent('''Multivariate discrete random variables are not yet supported.''')) conditional_domain = reduce_rational_inequalities_wrap(condition, rvs[0]) conditional_domain = conditional_domain.intersect(self.domain.set) return SingleDiscreteDomain(rvs[0].symbol, conditional_domain)
def _compound_check(self, dist): """ Checks if the given distribution contains random parameters. """ randoms = [] for arg in dist.args: randoms.extend(random_symbols(arg)) if len(randoms) == 0: return False return True
def restricted_domain(self, condition): rvs = random_symbols(condition) assert all(r.symbol in self.symbols for r in rvs) if (len(rvs) > 1): raise NotImplementedError(filldedent('''Multivariate discrete random variables are not yet supported.''')) conditional_domain = reduce_rational_inequalities_wrap(condition, rvs[0]) conditional_domain = conditional_domain.intersect(self.domain.set) return conditional_domain
def compute_density(self, expr): if self._is_symbolic: rv = list(random_symbols(expr))[0] k = Dummy('k', integer=True) cond = True if not isinstance(expr, (Relational, Logic)) \ else expr.subs(rv, k) return Lambda(k, Piecewise((self.pmf(k), And(k >= self.args[1].low, k <= self.args[1].high, cond)), (0, True))) expr = rv_subs(expr, self.values) return FinitePSpace(self.domain, self.distribution).compute_density(expr)
def pdf(self, *x): expr, rvs = self.args[0], self.args[1] marginalise_out = [i for i in random_symbols(expr) if i not in rvs] if isinstance(expr, JointDistribution): count = len(expr.domain.args) x = Dummy('x', real=True, finite=True) syms = tuple(Indexed(x, i) for i in count) expr = expr.pdf(syms) else: syms = tuple(rv.pspace.symbol if isinstance(rv, RandomSymbol) else rv.args[0] for rv in rvs) return Lambda(syms, self.compute_pdf(expr, marginalise_out))(*x)
def compute_expectation(self, expr, rvs=None, **kwargs): if self._is_symbolic: rv = random_symbols(expr)[0] k = Dummy('k', integer=True) expr = expr.subs(rv, k) cond = True if not isinstance(expr, (Relational, Logic)) \ else expr func = self.pmf(k) * k if cond != True else self.pmf(k) * expr return Sum(Piecewise((func, cond), (0, True)), (k, self.distribution.low, self.distribution.high)).doit() expr = rv_subs(expr, rvs) return FinitePSpace(self.domain, self.distribution).compute_expectation(expr, rvs, **kwargs)
def probability(self, condition): cond_symbols = frozenset(rs.symbol for rs in random_symbols(condition)) cond = rv_subs(condition) if not cond_symbols.issubset(self.symbols): raise ValueError("Cannot compare foreign random symbols, %s" %(str(cond_symbols - self.symbols))) if isinstance(condition, Relational) and \ (not cond.free_symbols.issubset(self.domain.free_symbols)): rv = condition.lhs if isinstance(condition.rhs, Symbol) else condition.rhs return sum(Piecewise( (self.prob_of(elem), condition.subs(rv, list(elem)[0][1])), (0, True)) for elem in self.domain) return sum(self.prob_of(elem) for elem in self.where(condition))
def JointRV(symbol, pdf, _set=None): """ Create a Joint Random Variable where each of its component is continuous, given the following: Parameters ========== symbol : Symbol Represents name of the random variable. pdf : A PDF in terms of indexed symbols of the symbol given as the first argument NOTE ==== As of now, the set for each component for a ``JointRV`` is equal to the set of all integers, which cannot be changed. Examples ======== >>> from sympy import exp, pi, Indexed, S >>> from sympy.stats import density, JointRV >>> x1, x2 = (Indexed('x', i) for i in (1, 2)) >>> pdf = exp(-x1**2/2 + x1 - x2**2/2 - S(1)/2)/(2*pi) >>> N1 = JointRV('x', pdf) #Multivariate Normal distribution >>> density(N1)(1, 2) exp(-2)/(2*pi) Returns ======= RandomSymbol """ #TODO: Add support for sets provided by the user symbol = sympify(symbol) syms = list(i for i in pdf.free_symbols if isinstance(i, Indexed) and i.base == IndexedBase(symbol)) syms = tuple(sorted(syms, key = lambda index: index.args[1])) _set = S.Reals**len(syms) pdf = Lambda(syms, pdf) dist = JointDistributionHandmade(pdf, _set) jrv = JointPSpace(symbol, dist).value rvs = random_symbols(pdf) if len(rvs) != 0: dist = MarginalDistribution(dist, (jrv,)) return JointPSpace(symbol, dist).value return jrv
def doit(self, **hints): deep = hints.get('deep', True) condition = self._condition expr = self.args[0] numsamples = hints.get('numsamples', False) for_rewrite = not hints.get('for_rewrite', False) if deep: expr = expr.doit(**hints) if not random_symbols(expr) or isinstance( expr, Expectation): # expr isn't random? return expr if numsamples: # Computing by monte carlo sampling? evalf = hints.get('evalf', True) return sampling_E(expr, condition, numsamples=numsamples, evalf=evalf) if expr.has(RandomIndexedSymbol): return pspace(expr).compute_expectation(expr, condition) # Create new expr and recompute E if condition is not None: # If there is a condition return self.func(given(expr, condition)).doit(**hints) # A few known statements for efficiency if expr.is_Add: # We know that E is Linear return Add(*[ self.func(arg, condition).doit( **hints) if not isinstance(arg, Expectation) else self. func(arg, condition) for arg in expr.args ]) if expr.is_Mul: if expr.atoms(Expectation): return expr if pspace(expr) == PSpace(): return self.func(expr) # Otherwise case is simple, pass work off to the ProbabilitySpace result = pspace(expr).compute_expectation(expr, evaluate=for_rewrite) if hasattr(result, 'doit') and for_rewrite: return result.doit(**hints) else: return result
def pdf(self, *x): expr, rvs = self.args[0], self.args[1] marginalise_out = [i for i in random_symbols(expr) if i not in self.args[1]] syms = [i.pspace.symbol for i in self.args[1]] for i in expr.atoms(Indexed): if isinstance(i, Indexed) and isinstance(i.base, RandomSymbol)\ and i not in rvs: marginalise_out.append(i) if isinstance(expr, CompoundDistribution): syms = Dummy('x', real=True) expr = expr.args[0].pdf(syms) elif isinstance(expr, JointDistribution): count = len(expr.domain.args) x = Dummy('x', real=True, finite=True) syms = [Indexed(x, i) for i in count] expr = expression.pdf(syms) return Lambda(syms, self.compute_pdf(expr, marginalise_out))(*x)
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): syms = tuple(self.value[i] for i in range(self.component_count)) rvs = rvs or syms if not any([i in rvs for i in syms]): return expr expr = expr*self.pdf for rv in rvs: if isinstance(rv, Indexed): expr = expr.xreplace({rv: Indexed(str(rv.base), rv.args[1])}) elif isinstance(rv, RandomSymbol): expr = expr.xreplace({rv: rv.symbol}) if self.value in random_symbols(expr): raise NotImplementedError(filldedent(''' Expectations of expression with unindexed joint random symbols cannot be calculated yet.''')) limits = tuple((Indexed(str(rv.base),rv.args[1]), self.distribution.set.args[rv.args[1]]) for rv in syms) return Integral(expr, *limits)
def JointRV(symbol, pdf, _set=None): """ Create a Joint Random Variable where each of its component is conitinuous, given the following: -- a symbol -- a PDF in terms of indexed symbols of the symbol given as the first argument NOTE: As of now, the set for each component for a `JointRV` is equal to the set of all integers, which can not be changed. Returns a RandomSymbol. Examples ======== >>> from sympy import symbols, exp, pi, Indexed, S >>> from sympy.stats import density >>> from sympy.stats.joint_rv_types import JointRV >>> x1, x2 = (Indexed('x', i) for i in (1, 2)) >>> pdf = exp(-x1**2/2 + x1 - x2**2/2 - S(1)/2)/(2*pi) >>> N1 = JointRV('x', pdf) #Multivariate Normal distribution >>> density(N1)(1, 2) exp(-2)/(2*pi) """ #TODO: Add support for sets provided by the user symbol = sympify(symbol) syms = list(i for i in pdf.free_symbols if isinstance(i, Indexed) and i.base == IndexedBase(symbol)) syms.sort(key = lambda index: index.args[1]) _set = S.Reals**len(syms) pdf = Lambda(syms, pdf) dist = JointDistributionHandmade(pdf, _set) jrv = JointPSpace(symbol, dist).value rvs = random_symbols(pdf) if len(rvs) != 0: dist = MarginalDistribution(dist, (jrv,)) return JointPSpace(symbol, dist).value return jrv
def probability(self, condition, **kwargs): z = Dummy('z', real=True) cond_inv = False if isinstance(condition, Ne): condition = Eq(condition.args[0], condition.args[1]) cond_inv = True # Univariate case can be handled by where try: domain = self.where(condition) rv = [rv for rv in self.values if rv.symbol == domain.symbol][0] # Integrate out all other random variables pdf = self.compute_density(rv, **kwargs) # return S.Zero if `domain` is empty set if domain.set is S.EmptySet or isinstance(domain.set, FiniteSet): return S.Zero if not cond_inv else S.One if isinstance(domain.set, Union): return sum( Integral(pdf(z), (z, subset), **kwargs) for subset in domain.set.args if isinstance(subset, Interval)) # Integrate out the last variable over the special domain return Integral(pdf(z), (z, domain.set), **kwargs) # Other cases can be turned into univariate case # by computing a density handled by density computation except NotImplementedError: from sympy.stats.rv import density expr = condition.lhs - condition.rhs if not random_symbols(expr): dens = self.density comp = condition.rhs else: dens = density(expr, **kwargs) comp = 0 if not isinstance(dens, ContinuousDistribution): from sympy.stats.crv_types import ContinuousDistributionHandmade dens = ContinuousDistributionHandmade(dens, set=self.domain.set) # Turn problem into univariate case space = SingleContinuousPSpace(z, dens) result = space.probability(condition.__class__(space.value, comp)) return result if not cond_inv else S.One - result
def probability(self, condition): cond_symbols = frozenset(rs.symbol for rs in random_symbols(condition)) assert cond_symbols.issubset(self.symbols) return sum(self.prob_of(elem) for elem in self.where(condition))
def where(self, condition): assert all(r.symbol in self.symbols for r in random_symbols(condition)) return ConditionalFiniteDomain(self.domain, condition)
def domain(self): rvs = random_symbols(self.distribution) if not rvs: return SingleDomain(self.symbol, self.distribution.set) return ProductDomain(*[rv.pspace.domain for rv in rvs])
def latent_distributions(self): return random_symbols(self.args[0])
def expectation(self, expr, condition=None, evaluate=True, **kwargs): """ Handles expectation queries for markov process. Parameters ========== expr: RandomIndexedSymbol, Relational, Logic Condition for which expectation has to be computed. Must contain a RandomIndexedSymbol of the process. condition: Relational, Logic The given conditions under which computations should be done. Returns ======= Expectation Unevaluated object if computations cannot be done due to insufficient information. Expr In all other cases when the computations are successful. Note ==== Any information passed at the time of query overrides any information passed at the time of object creation like transition probabilities, state space. Pass the transition matrix using TransitionMatrixOf, generator matrix using GeneratorMatrixOf and state space using StochasticStateSpaceOf in given_condition using & or And. """ check, mat, state_space, condition = \ self._preprocess(condition, evaluate) if check: return Expectation(expr, condition) rvs = random_symbols(expr) if isinstance(expr, Expr) and isinstance(condition, Eq) \ and len(rvs) == 1: # handle queries similar to E(f(X[i]), Eq(X[i-m], <some-state>)) rv = list(rvs)[0] lhsg, rhsg = condition.lhs, condition.rhs if not isinstance(lhsg, RandomIndexedSymbol): lhsg, rhsg = (rhsg, lhsg) if rhsg not in self.state_space: raise ValueError("%s state is not in the state space." % (rhsg)) if rv.key < lhsg.key: raise ValueError( "Incorrect given condition is given, expectation " "time %s < time %s" % (rv.key, rv.key)) mat_of = TransitionMatrixOf(self, mat) if isinstance( self, DiscreteMarkovChain) else GeneratorMatrixOf(self, mat) cond = condition & mat_of & \ StochasticStateSpaceOf(self, state_space) func = lambda s: self.probability(Eq(rv, s), cond) * expr.subs( rv, s) return sum([func(s) for s in state_space]) raise NotImplementedError( "Mechanism for handling (%s, %s) queries hasn't been " "implemented yet." % (expr, condition))
def domain(self): rvs = random_symbols(self.distribution) if len(rvs) == 0: return SingleDomain(self.symbol, self.set) return ProductDomain(*[rv.pspace.domain for rv in rvs])