docs: def calc_neg_log_likelihood_and_neg_gradient(beta,
                                               design_3d,
                                               alt_IDs,
                                               rows_to_obs,
                                               rows_to_alts,
                                               rows_to_mixers,
                                               choice_vector,
                                               utility_transform,
                                               constrained_pos,
                                               ridge=None,
                                               weights=None,
                                               *args):
      """
      Parameters
      ----------
      beta : 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. Should have 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. Should have 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.
      constrained_pos : list of ints, or None, optional.
          Each int denotes a position in the array of estimated parameters that
          are not to change from their initial values. None of the integers
          should be greater than `beta.size`.
      ridge : int, float, long, or None, optional.
          Determines whether or not ridge regression is performed. If a float is
          passed, then that float 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
      -------
      tuple. (`neg_log_likelihood`, `neg_beta_gradient_vec`).
          The first element is a float. The second element is a 1D numpy array of
          shape (design.shape[1],). The first element is the negative
          log-likelihood of this model evaluated at the passed values of beta.
          The second element is the gradient of the negative log- likelihood with
          respect to the vector of utility coefficients.
      """
      neg_log_likelihood = -1 * calc_mixed_log_likelihood(beta,
                                                          design_3d,
                                                          alt_IDs,
                                                          rows_to_obs,
                                                          rows_to_alts,
                                                          rows_to_mixers,
                                                          choice_vector,
                                                          utility_transform,
                                                          ridge=ridge,
                                                          weights=weights)
  
      neg_beta_gradient_vec = -1 * calc_mixed_logit_gradient(beta,
                                                             design_3d,
                                                             alt_IDs,
                                                             rows_to_obs,
                                                             rows_to_alts,
                                                             rows_to_mixers,
                                                             choice_vector,
                                                             utility_transform,
                                                             ridge=ridge,
                                                             weights=weights)
  
      if constrained_pos is not None:
          neg_beta_gradient_vec[constrained_pos] = 0
  
      return neg_log_likelihood, neg_beta_gradient_vec