docs: def cause_repertoire(self, mechanism, purview):
          """Return the cause repertoire of a mechanism over a purview.
  
          Args:
              mechanism (tuple[int]): The mechanism for which to calculate the
                  cause repertoire.
              purview (tuple[int]): The purview over which to calculate the
                  cause repertoire.
  
          Returns:
              np.ndarray: The cause repertoire of the mechanism over the purview.
  
          .. note::
              The returned repertoire is a distribution over purview node states,
              not the states of the whole network.
          """
          # If the purview is empty, the distribution is empty; return the
          # multiplicative identity.
          if not purview:
              return np.array([1.0])
          # If the mechanism is empty, nothing is specified about the previous
          # state of the purview; return the purview's maximum entropy
          # distribution.
          if not mechanism:
              return max_entropy_distribution(purview, self.tpm_size)
          # Use a frozenset so the arguments to `_single_node_cause_repertoire`
          # can be hashed and cached.
          purview = frozenset(purview)
          # Preallocate the repertoire with the proper shape, so that
          # probabilities are broadcasted appropriately.
          joint = np.ones(repertoire_shape(purview, self.tpm_size))
          # The cause repertoire is the product of the cause repertoires of the
          # individual nodes.
          joint *= functools.reduce(
              np.multiply, [self._single_node_cause_repertoire(m, purview)
                            for m in mechanism]
          )
          # The resulting joint distribution is over previous states, which are
          # rows in the TPM, so the distribution is a column. The columns of a
          # TPM don't necessarily sum to 1, so we normalize.
          return distribution.normalize(joint)