def probability(self, condition, given_condition=None, evaluate=True, **kwargs): """ Handles probability queries for discrete Markov chains. Parameters ========== condition: Relational given_condition: Relational/And Returns ======= Probability If the transition probabilities are not available Expr If the transition probabilities is MatrixSymbol or Matrix 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 and state space using StochasticStateSpaceOf in given_condition using & or And. """ check, trans_probs, state_space, given_condition = \ self._preprocess(given_condition, evaluate) if check: return Probability(condition, given_condition) if isinstance(condition, Eq) and \ isinstance(given_condition, Eq) and \ len(given_condition.atoms(RandomSymbol)) == 1: # handles simple queries like P(Eq(X[i], dest_state), Eq(X[i], init_state)) lhsc, rhsc = condition.lhs, condition.rhs lhsg, rhsg = given_condition.lhs, given_condition.rhs if not isinstance(lhsc, RandomIndexedSymbol): lhsc, rhsc = (rhsc, lhsc) if not isinstance(lhsg, RandomIndexedSymbol): lhsg, rhsg = (rhsg, lhsg) keyc, statec, keyg, stateg = (lhsc.key, rhsc, lhsg.key, rhsg) if Lt(stateg, trans_probs.shape[0]) == False or Lt( statec, trans_probs.shape[1]) == False: raise IndexError( "No information is available for (%s, %s) in " "transition probabilities of shape, (%s, %s). " "State space is zero indexed." % (stateg, statec, trans_probs.shape[0], trans_probs.shape[1])) if keyc < keyg: raise ValueError( "Incorrect given condition is given, probability " "of past state cannot be computed from future state.") nsteptp = trans_probs**(keyc - keyg) if hasattr(nsteptp, "__getitem__"): return nsteptp.__getitem__((stateg, statec)) return Indexed(nsteptp, stateg, statec) info = TransitionMatrixOf(self, trans_probs) & StochasticStateSpaceOf( self, state_space) new_gc = given_condition & info if isinstance(condition, And): # handle queries like, # P(Eq(X[i+k], s1) & Eq(X[i+m], s2) . . . & Eq(X[i], sn), Eq(P(Eq(X[i], si)), prob)) conds = condition.args idx2state = dict() for cond in conds: idx, state = (cond.lhs, cond.rhs) if isinstance(cond.lhs, RandomIndexedSymbol) else \ (cond.rhs, cond.lhs) idx2state[idx] = cond if idx2state.get(idx, None) is None else \ idx2state[idx] & cond if any( len(Intersection(idx2state[idx].as_set(), state_space)) != 1 for idx in idx2state): return S.Zero # a RandomIndexedSymbol cannot go to different states simultaneously i, result = -1, 1 conds = And.fromiter( Intersection(idx2state[idx].as_set(), state_space).as_relational(idx) for idx in idx2state) if not isinstance(conds, And): return self.probability(conds, new_gc) conds = conds.args while i > -len(conds): result *= self.probability(conds[i], conds[i - 1] & info) i -= 1 if isinstance(given_condition, (TransitionMatrixOf, StochasticStateSpaceOf)): return result * Probability(conds[i]) if isinstance(given_condition, And): idx_sym = conds[i].atoms(RandomIndexedSymbol) prob, count = S(0), 0 for gc in given_condition.args: if gc.atoms(RandomIndexedSymbol) == idx_sym: prob += gc.rhs if isinstance(gc.lhs, Probability) else gc.lhs count += 1 if isinstance(state_space, FiniteSet) and \ count == len(state_space) - 1: given_condition = Eq(Probability(conds[i]), S(1) - prob) if isinstance(given_condition, Eq): if not isinstance(given_condition.lhs, Probability) or \ given_condition.lhs.args[0] != conds[i]: raise ValueError("Probability for %s needed", conds[i]) return result * given_condition.rhs if isinstance(condition, Or): conds, prob_sum = condition.args, S(0) idx2state = dict() for cond in conds: idx, state = (cond.lhs, cond.rhs) if isinstance(cond.lhs, RandomIndexedSymbol) else \ (cond.rhs, cond.lhs) idx2state[idx] = cond if idx2state.get(idx, None) is None else \ idx2state[idx] | cond conds = Or.fromiter( Intersection(idx2state[idx].as_set(), state_space).as_relational(idx) for idx in idx2state) if not isinstance(conds, Or): return self.probability(conds, new_gc) return sum([self.probability(cond, new_gc) for cond in conds.args]) if isinstance(condition, Ne): prob = self.probability(Not(condition), new_gc) return S(1) - prob raise NotImplementedError( "Mechanism for handling (%s, %s) queries hasn't been " "implemented yet." % (condition, given_condition))