docs: def fit_mle(self,
                  init_vals,
                  init_shapes=None,
                  init_intercepts=None,
                  init_coefs=None,
                  print_res=True,
                  method="BFGS",
                  loss_tol=1e-06,
                  gradient_tol=1e-06,
                  maxiter=1000,
                  ridge=None,
                  constrained_pos=None,
                  just_point=False,
                  **kwargs):
          """
          Parameters
          ----------
          init_vals : 1D ndarray.
              The initial values to start the optimization process with. There
              should be one value for each index coefficient and shape
              parameter being estimated. Shape parameters should come before
              intercept parameters, which should come before index coefficients.
              One can also pass None, and instead pass `init_shapes`, optionally
              `init_intercepts` if `"intercept"` is not in the utility
              specification, and `init_coefs`.
          init_shapes : 1D ndarray or None, optional.
              The initial values of the shape parameters. All elements should be
              ints, floats, or longs. There should be one parameter per possible
              alternative id in the dataset. This keyword argument will be
              ignored if `init_vals` is not None. Default == None.
          init_intercepts : 1D ndarray or None, optional.
              The initial values of the intercept parameters. There should be one
              parameter per possible alternative id in the dataset, minus one.
              The passed values for this argument will be ignored if `init_vals`
              is not None. This keyword argument should only be used if
              `"intercept"` is not in the utility specification. Default == None.
          init_coefs : 1D ndarray or None, optional.
              The initial values of the index coefficients. There should be one
              coefficient per index variable. The passed values for this argument
              will be ignored if `init_vals` is not None. Default == None.
          print_res : bool, optional.
              Determines whether the timing and initial and final log likelihood
              results will be printed as they they are determined.
              Default `== True`.
          method : str, optional.
              Should be a valid string for scipy.optimize.minimize. Determines
              the optimization algorithm that is used for this problem.
              Default `== 'bfgs'`.
          loss_tol : float, optional.
              Determines the tolerance on the difference in objective function
              values from one iteration to the next that is needed to determine
              convergence. Default `== 1e-06`.
          gradient_tol : float, optional.
              Determines the tolerance on the difference in gradient values from
              one iteration to the next which is needed to determine convergence.
              Default `== 1e-06`.
          maxiter : int, optional.
              Determines the maximum number of iterations used by the optimizer.
              Default `== 1000`.
          ridge : int, float, long, 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. The scalar should be greater than or equal to
              zero. Default `== None`.
          constrained_pos : list or None, optional.
              Denotes the positions of the array of estimated parameters that are
              not to change from their initial values. If a list is passed, the
              elements are to be integers where no such integer is greater than
              `init_vals.size.` Default == None.
          just_point : bool, optional.
              Determines whether (True) or not (False) calculations that are non-
              critical for obtaining the maximum likelihood point estimate will
              be performed. If True, this function will return the results
              dictionary from scipy.optimize. Default == False.
  
          Returns
          -------
          None. Estimation results are saved to the model instance.
          """
          # Store the optimization method
          self.optimization_method = method
  
          # Store the ridge parameter
          self.ridge_param = ridge
          if ridge is not None:
              warnings.warn(_ridge_warning_msg)
  
          # Construct the mappings from alternatives to observations and from
          # chosen alternatives to observations
          mapping_res = self.get_mappings_for_fit()
          rows_to_alts = mapping_res["rows_to_alts"]
  
          # Create init_vals from init_coefs, init_intercepts, and init_shapes if
          # those arguments are passed to the function and init_vals is None.
          if init_vals is None and all([x is not None for x in [init_shapes,
                                                                init_coefs]]):
              ##########
              # Check the integrity of the parameter kwargs
              ##########
              num_alternatives = rows_to_alts.shape[1]
              try:
                  assert init_shapes.shape[0] == num_alternatives
              except AssertionError:
                  msg = "init_shapes is of length {} but should be of length {}"
                  raise ValueError(msg.format(init_shapes.shape,
                                              num_alternatives))
  
              try:
                  assert init_coefs.shape[0] == self.design.shape[1]
              except AssertionError:
                  msg = "init_coefs has length {} but should have length {}"
                  raise ValueError(msg.format(init_coefs.shape,
                                              self.design.shape[1]))
  
              try:
                  if init_intercepts is not None:
                      assert init_intercepts.shape[0] == (num_alternatives - 1)
              except AssertionError:
                  msg = "init_intercepts has length {} but should have length {}"
                  raise ValueError(msg.format(init_intercepts.shape,
                                              num_alternatives - 1))
  
              # The code block below will limit users to only having 'inside'
              # OR 'outside' intercept parameters but not both.
              # try:
              #     condition_1 = "intercept" not in self.specification
              #     condition_2 = init_intercepts is None
              #     assert condition_1 or condition_2
              # except AssertionError as e:
              #     msg = "init_intercepts should only be used if 'intercept' is"
              #     msg_2 = " not in one's index specification."
              #     msg_3 = "Either make init_intercepts = None or remove "
              #     msg_4 = "'intercept' from the specification."
              #     print(msg + msg_2 )
              #     print(msg_3 + msg_4)
              #     raise e
  
              if init_intercepts is not None:
                  init_vals = np.concatenate((init_shapes,
                                              init_intercepts,
                                              init_coefs), axis=0)
              else:
                  init_vals = np.concatenate((init_shapes,
                                              init_coefs), axis=0)
          elif init_vals is None:
              msg = "If init_vals is None, then users must pass both init_coefs "
              msg_2 = "and init_shapes."
              raise ValueError(msg + msg_2)
  
          # Create the estimation object
          zero_vector = np.zeros(init_vals.shape)
          scobit_estimator = ScobitEstimator(self,
                                             mapping_res,
                                             ridge,
                                             zero_vector,
                                             split_param_vec,
                                             constrained_pos=constrained_pos)
          # Set the derivative functions for estimation
          scobit_estimator.set_derivatives()
  
          # Perform one final check on the length of the initial values
          scobit_estimator.check_length_of_initial_values(init_vals)
  
          # Get the estimation results
          estimation_res = estimate(init_vals,
                                    scobit_estimator,
                                    method,
                                    loss_tol,
                                    gradient_tol,
                                    maxiter,
                                    print_res,
                                    just_point=just_point)
  
          if not just_point:
              # Store the estimation results
              self.store_fit_results(estimation_res)
  
              return None
          else:
              return estimation_res