Example #1
0
 def parent_values(
         self, cbn: CausalBayesianNetwork) -> Iterator[Dict[str, Outcome]]:
     """Return a list of lists for the values each parent can take (based on the parent state names)"""
     parent_values_list = []
     for p in cbn.get_parents(self.variable):
         try:
             parent_values_list.append(cbn.model.domain[p])
         except KeyError:
             raise ParentsNotReadyException(
                 f"Parent {p} of {self.variable} not yet instantiated")
     for parent_values in itertools.product(*parent_values_list):
         yield {
             p.lower(): parent_values[i]
             for i, p in enumerate(cbn.get_parents(self.variable))
         }
Example #2
0
    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})
Example #3
0
 def check_function_arguments_match_parent_names(
         self, cbn: CausalBayesianNetwork) -> None:
     """Raises a ValueError if the parents in the CID don't match the argument to the specified function"""
     sig = inspect.signature(self.stochastic_function).parameters
     arg_kinds = [arg_kind.kind.name for arg_kind in sig.values()]
     args = set(sig)
     lower_case_parents = {
         p.lower()
         for p in cbn.get_parents(self.variable)
     }
     if "VAR_KEYWORD" not in arg_kinds and args != lower_case_parents:
         raise ValueError(
             f"function for {self.variable} mismatch parents on"
             f" {args.symmetric_difference(lower_case_parents)}, ")
Example #4
0
    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}
        )