def initialize_tabular_cpd(self, cbn: CausalBayesianNetwork) -> None: """Initialize the probability table for the inherited TabularCPD. Requires that all parents in the CID have already been instantiated. """ self.check_function_arguments_match_parent_names(cbn) self.cbn = cbn if self.force_domain: if not set(self.possible_values(cbn)).issubset(self.force_domain): raise ValueError( "variable {} can take value outside given state_names". format(self.variable)) domain: Sequence[ Outcome] = self.force_domain if self.force_domain else self.possible_values( cbn) def complete_prob_dictionary( prob_dictionary: Dict[Outcome, Union[int, float]] ) -> Dict[Outcome, Union[int, float]]: """Complete a probability dictionary with probabilities for missing outcomes""" missing_outcomes = set(domain) - set(prob_dictionary.keys()) missing_prob_mass = 1 - sum( prob_dictionary.values()) # type: ignore for outcome in missing_outcomes: prob_dictionary[outcome] = missing_prob_mass / len( missing_outcomes) return prob_dictionary card = len(domain) evidence = cbn.get_parents(self.variable) evidence_card = [cbn.get_cardinality(p) for p in evidence] probability_list = [] for pv in self.parent_values(cbn): probabilities = complete_prob_dictionary( self.stochastic_function(**pv)) probability_list.append([probabilities[t] for t in domain]) probability_matrix = np.array(probability_list).T if not np.allclose(probability_matrix.sum(axis=0), 1, atol=0.01): raise ValueError( f"The values for {self.variable} do not sum to 1 \n{probability_matrix}" ) if (probability_matrix < 0).any() or (probability_matrix > 1).any(): raise ValueError( f"The probabilities for {self.variable} are not within range 0-1\n{probability_matrix}" ) self.domain = domain super().__init__(self.variable, card, probability_matrix, evidence, evidence_card, state_names={self.variable: self.domain})
def __init__( self, variable: str, stochastic_function: Callable[..., Union[Outcome, Mapping[Outcome, Union[int, float]]]], cbn: CausalBayesianNetwork, domain: Optional[Sequence[Outcome]] = None, label: str = None, ) -> None: """Initialize StochasticFunctionCPD with a variable name and a stochastic function. Parameters ---------- variable: The variable name. stochastic_function: A stochastic function that maps parent outcomes to a distribution over outcomes for this variable (see doc-string for class). The different parents are identified by name: the arguments to the function must be lowercase versions of the names of the parent variables. For example, if X has parents Y, S1, and Obs, the arguments to function must be y, s1, and obs. domain: An optional specification of the variable's domain. Must include all values this variable can take as a result of its function. label: An optional label used to describe this distribution. """ self.variable = variable self.func = stochastic_function self.cbn = cbn assert isinstance(domain, (list, type(None))) self.force_domain: Optional[Sequence[Outcome]] = domain assert isinstance(label, (str, type(None))) self.label = label if label is not None else self.compute_label(stochastic_function) self.check_function_arguments_match_parent_names() if self.force_domain: if not set(self.possible_values()).issubset(self.force_domain): raise ValueError("variable {} can take value outside given state_names".format(self.variable)) self.domain = self.force_domain if self.force_domain else self.possible_values() def complete_prob_dictionary( prob_dictionary: Mapping[Outcome, Union[int, float]] ) -> Mapping[Outcome, Union[int, float]]: """Complete a probability dictionary with probabilities for missing outcomes""" prob_dictionary = {key: value for key, value in prob_dictionary.items() if value is not None} missing_outcomes = set(self.domain) - set(prob_dictionary.keys()) missing_prob_mass = 1 - sum(prob_dictionary.values()) # type: ignore for outcome in missing_outcomes: prob_dictionary[outcome] = missing_prob_mass / len(missing_outcomes) return prob_dictionary card = len(self.domain) evidence = cbn.get_parents(self.variable) evidence_card = [cbn.get_cardinality(p) for p in evidence] probability_list = [] for pv in self.parent_values(): probabilities = complete_prob_dictionary(self.stochastic_function(**pv)) probability_list.append([probabilities[t] for t in self.domain]) probability_matrix = np.array(probability_list).T if not np.allclose(probability_matrix.sum(axis=0), 1, rtol=0, atol=0.01): # type: ignore raise ValueError(f"The values for {self.variable} do not sum to 1 \n{probability_matrix}") if (probability_matrix < 0).any() or (probability_matrix > 1).any(): # type: ignore raise ValueError(f"The probabilities for {self.variable} are not within range 0-1\n{probability_matrix}") super().__init__( self.variable, card, probability_matrix, evidence, evidence_card, state_names={self.variable: self.domain} )