docs: def _cloglog_utility_transform(systematic_utilities,
                                 alt_IDs,
                                 rows_to_alts,
                                 shape_params,
                                 intercept_params,
                                 intercept_ref_pos=None,
                                 *args, **kwargs):
      """
      Parameters
      ----------
      systematic_utilities : 1D ndarray.
          All elements should be ints, floats, or longs. Should contain the
          systematic utilities of each observation per available alternative.
          Note that this vector is formed by the dot product of the design matrix
          with the vector of utility coefficients.
      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_alts : 2D scipy sparse matrix.
          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. All
          elements should be zeros or ones.
      shape_params : None or 1D ndarray.
          Should be None since the clog-log model has no shape parameters.
      intercept_params : None or 1D ndarray.
          If an array, each element should be an int, float, or long. If J is the
          total number of possible alternatives for the dataset being modeled,
          there should be J-1 elements in the array. Use None if no outside
          intercepts are being estimated.
      intercept_ref_pos : int, or None, optional.
          Specifies the index of the alternative, in the ordered array of unique
          alternatives, that is not having its intercept parameter estimated (in
          order to ensure identifiability). Should only be None if
          `intercept_params` is None.
  
      Returns
      -------
      transformations : 2D ndarray.
          Should have shape `(systematic_utilities.shape[0], 1)`. The returned
          array contains the transformed utility values for this model. All
          elements will be ints, longs, or floats.
      """
      # Calculate the data dependent part of the transformation
      # Also, along the way, guard against numeric underflow or overflow
      exp_v = np.exp(systematic_utilities)
      # exp_v[np.isposinf(exp_v)] = max_comp_value
  
      exp_exp_v = np.exp(exp_v)
      # exp_exp_v[np.isposinf(exp_exp_v)] = max_comp_value
  
      # Calculate the transformed systematic utilities
      transformations = np.log(exp_exp_v - 1)
      # Guard against underflow
      transformations[np.isneginf(transformations)] = -1 * max_comp_value
      # Guard against overflow when systematic utilities are moderately large
      too_big_idx = np.where(systematic_utilities >= 3.7)
      transformations[too_big_idx] = np.exp(systematic_utilities[too_big_idx])
      # Guard against overflow when systematic utilities are completely too big.
      inf_idx = np.isposinf(transformations)
      transformations[inf_idx] = max_comp_value
  
      # Account for the outside intercept parameters if there are any.
      if intercept_params is not None and intercept_ref_pos is not None:
          # Get a list of all the indices (or row indices) corresponding to the
          # alternatives whose intercept parameters are being estimated.
          needed_idxs = range(rows_to_alts.shape[1])
          needed_idxs.remove(intercept_ref_pos)
  
          if len(intercept_params.shape) > 1 and intercept_params.shape[1] > 1:
              # Get an array of zeros with shape
              # (num_possible_alternatives, num_parameter_samples)
              all_intercepts = np.zeros((rows_to_alts.shape[1],
                                         intercept_params.shape[1]))
              # For alternatives having their intercept estimated, replace the
              # zeros with the current value of the estimated intercepts
              all_intercepts[needed_idxs, :] = intercept_params
          else:
              # Get an array of zeros with shape (num_possible_alternatives,)
              all_intercepts = np.zeros(rows_to_alts.shape[1])
              # For alternatives having their intercept estimated, replace the
              # zeros with the current value of the estimated intercepts
              all_intercepts[needed_idxs] = intercept_params
  
          # Add the intercept values to f(x, beta, c)
          transformations += rows_to_alts.dot(all_intercepts)
  
      # Be sure to return a 2D array since other functions will be expecting that
      if len(transformations.shape) == 1:
          transformations = transformations[:, None]
  
      return transformations