docs: def macro_tpm_sbs(self, state_by_state_micro_tpm):
          """Create a state-by-state coarse-grained macro TPM.
  
          Args:
              micro_tpm (nd.array): The state-by-state TPM of the micro-system.
  
          Returns:
              np.ndarray: The state-by-state TPM of the macro-system.
          """
          validate.tpm(state_by_state_micro_tpm, check_independence=False)
  
          mapping = self.make_mapping()
  
          num_macro_states = 2 ** len(self.macro_indices)
          macro_tpm = np.zeros((num_macro_states, num_macro_states))
  
          micro_states = range(2 ** len(self.micro_indices))
          micro_state_transitions = itertools.product(micro_states, repeat=2)
  
          # For every possible micro-state transition, get the corresponding
          # previous and next macro-state using the mapping and add that
          # probability to the state-by-state macro TPM.
          for previous_state, current_state in micro_state_transitions:
              macro_tpm[mapping[previous_state], mapping[current_state]] += (
                  state_by_state_micro_tpm[previous_state, current_state])
  
          # Re-normalize each row because we're going from larger to smaller TPM
          return np.array([distribution.normalize(row) for row in macro_tpm])