docs: def calc_mixed_log_likelihood(params,
                                design_3d,
                                alt_IDs,
                                rows_to_obs,
                                rows_to_alts,
                                rows_to_mixers,
                                choice_vector,
                                utility_transform,
                                ridge=None,
                                weights=None):
      """
      Parameters
      ----------
      params : 1D ndarray.
          All elements should by ints, floats, or longs. Should have 1 element
          for each utility coefficient being estimated (i.e. num_features  +
          num_coefs_being_mixed).
      design_3d : 3D ndarray.
          All elements should be ints, floats, or longs. Should have one row per
          observation per available alternative. The second axis should have as
          many elements as there are draws from the mixing distributions of the
          coefficients. The last axis should have one element per index
          coefficient being estimated.
      alt_IDs : 1D ndarray.
          All elements should be ints. There should be one row per obervation per
          available alternative for the given observation. Elements denote the
          alternative corresponding to the given row of the design matrix.
      rows_to_obs : 2D scipy sparse array.
          All elements should be zeros and ones. There should be one row per
          observation per available alternative and one column per observation.
          This matrix maps the rows of the design matrix to the unique
          observations (on the columns).
      rows_to_alts : 2D scipy sparse array.
          All elements should be zeros and ones. There should be one row per
          observation per available alternative and one column per possible
          alternative. This matrix maps the rows of the design matrix to the
          possible alternatives for this dataset.
      rows_to_mixers : 2D scipy sparse array.
          All elements should be zeros and ones. Will map the rows of the design
          matrix to the particular units that the mixing is being performed over.
          Note that in the case of panel data, this matrix will be different from
          `rows_to_obs`.
      choice_vector : 1D ndarray.
          All elements should be either ones or zeros. There should be one row
          per observation per available alternative for the given observation.
          Elements denote the alternative which is chosen by the given
          observation with a 1 and a zero otherwise.
      utility_transform :  callable.
          Should accept a 1D array of systematic utility values, a 1D array of
          alternative IDs, and miscellaneous args and kwargs. Should return a 2D
          array whose elements contain the appropriately transformed systematic
          utility values, based on the current model being evaluated and the
          given draw of the random coefficients. There should be one column for
          each draw of the random coefficients. There should have one row per
          individual per choice situation per available alternative.
      ridge : scalar or None, optional.
          Determines whether or not ridge regression is performed. If a scalar is
          passed, then that scalar determines the ridge penalty for the
          optimization. Default = None.
      weights : 1D ndarray or None.
          Allows for the calculation of weighted log-likelihoods. The weights can
          represent various things. In stratified samples, the weights may be
          the proportion of the observations in a given strata for a sample in
          relation to the proportion of observations in that strata in the
          population. In latent class models, the weights may be the probability
          of being a particular class.
  
      Returns
      -------
      log_likelihood: float.
          The log-likelihood of the mixed logit model evaluated at the passed
          values of `params`.
      """
      # Calculate the weights for the sample
      if weights is None:
          weights = np.ones(design_3d.shape[0])
      weights_per_obs =\
          np.max(rows_to_mixers.toarray() * weights[:, None], axis=0)
  
      # Calculate the regular probability array. Note the implicit assumption
      # that params == index coefficients.
      prob_array = general_calc_probabilities(params,
                                              design_3d,
                                              alt_IDs,
                                              rows_to_obs,
                                              rows_to_alts,
                                              utility_transform,
                                              return_long_probs=True)
  
      # Calculate the simulated probability of correctly predicting each persons
      # sequence of choices. Note that this function implicitly assumes that the
      # mixing unit is the individual
      simulated_sequence_probs = calc_choice_sequence_probs(prob_array,
                                                            choice_vector,
                                                            rows_to_mixers)
  
      # Calculate the log-likelihood of the dataset
      log_likelihood = weights_per_obs.dot(np.log(simulated_sequence_probs))
  
      # Adujust for the presence of a ridge estimator. Again, note that we are
      # implicitly assuming that the only model being mixed is the MNL model,
      # such that params == index coefficients.
      if ridge is None:
          return log_likelihood
      else:
          return log_likelihood - ridge * np.square(params).sum()