docs: def _asym_utility_transform(systematic_utilities,
                              alt_IDs,
                              rows_to_alts,
                              eta,
                              intercept_params,
                              shape_ref_position=None,
                              intercept_ref_pos=None,
                              *args, **kwargs):
      """
      Parameters
      ----------
      systematic_utilities : 1D ndarray.
          Contains the systematic utilities for each each available alternative
          for each observation. All elements should be ints, floats, or longs.
      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 ndarray.
          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.
      eta : 1D ndarray.
          Each element should be an int, float, or long. There should be one
          value per transformed shape parameter. Note that if there are J
          possible alternatives in the dataset, then there should be J - 1
          elements in `eta`.
      intercept_params : 1D ndarray or None.
          If an array, each element should be an int, float, or long. For
          identifiability, there should be J- 1 elements where J is the total
          number of observed alternatives for this dataset.
      shape_ref_position : int.
          Specifies the position in the array of natural shape parameters that
          should be equal to 1 - the sum of the other elements. Specifies the
          alternative in the ordered array of unique alternatives that is not
          having its shape parameter estimated (to ensure identifiability).
      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. Default == None.
  
      Returns
      -------
      transformed_utilities : 2D ndarray.
          Should have shape `(systematic_utilities.shape[0], 1)`. The returned
          array contains the values of the transformed index for this model.
      """
      ##########
      # Convert the reduced shape parameters to the natural shape parameters
      ##########
      natural_shape_params = _convert_eta_to_c(eta, shape_ref_position)
  
      ##########
      # Calculate the transformed utilities from the natural shape parameters
      ##########
      # Create a vector which contains the appropriate shape for each row in
      # the design matrix
      long_shapes = rows_to_alts.dot(natural_shape_params)
  
      # Determine the total number of alternatives
      num_alts = rows_to_alts.shape[1]
  
      # Get the natural log of the long_shapes
      log_long_shapes = np.log(long_shapes)
      # Guard against underflow, aka long_shapes too close to zero
      log_long_shapes[np.isneginf(log_long_shapes)] = -1 * max_comp_value
  
      # Get the natural log of (1 - long_shapes) / (J - 1)
      log_1_sub_long_shapes = np.log((1 - long_shapes) / float(num_alts - 1))
      # Guard against underflow, aka 1 - long_shapes too close to zero
      small_idx = np.isneginf(log_1_sub_long_shapes)
      log_1_sub_long_shapes[small_idx] = -1 * max_comp_value
  
      # Compute the transformed utilities
      multiplier = ((systematic_utilities >= 0) * log_long_shapes +
                    (systematic_utilities < 0) * log_1_sub_long_shapes)
      transformed_utilities = log_long_shapes - systematic_utilities * multiplier
  
      # Perform a guard for shape --> 1 and V --> infinity.
      # It is DEFINITELY not clear if this is the correct thing to do. The limit
      # might not even exist, and there is no clear multivariate L'Hopital's
      # rule. So this is an arbitrary decision
      weird_case = np.isposinf(systematic_utilities) * (long_shapes == 1)
      transformed_utilities[weird_case] = 0
  
      # 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)
          transformed_utilities += rows_to_alts.dot(all_intercepts)
  
      # Perform final guards against over/underflow in the transformations
      transformed_utilities[np.isposinf(transformed_utilities)] = max_comp_value
      transformed_utilities[np.isneginf(transformed_utilities)] = -max_comp_value
  
      # Be sure to return a 2D array since other functions will be expecting that
      if len(transformed_utilities.shape) == 1:
          transformed_utilities = transformed_utilities[:, np.newaxis]
  
      return transformed_utilities