docs: def estimate(init_values,
               estimator,
               method,
               loss_tol,
               gradient_tol,
               maxiter,
               print_results,
               use_hessian=True,
               just_point=False,
               **kwargs):
      """
      Estimate the given choice model that is defined by `estimator`.
  
      Parameters
      ----------
      init_vals : 1D ndarray.
          Should contain the initial values to start the optimization process
          with.
      estimator : an instance of the EstimationObj class.
      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`.
      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`.
      use_hessian : bool, optional.
          Determines whether the `calc_neg_hessian` method of the `estimator`
          object will be used as the hessian function during the estimation. This
          kwarg is used since some models (such as the Mixed Logit and Nested
          Logit) use a rather crude (i.e. the BHHH) approximation to the Fisher
          Information Matrix, and users may prefer to not use this approximation
          for the hessian during estimation.
      just_point : bool, optional.
          Determines whether or not calculations that are non-critical for
          obtaining the maximum likelihood point estimate will be performed.
          Default == False.
  
      Return
      ------
      results : dict.
          The dictionary of estimation results that is returned by
          scipy.optimize.minimize. It will also have (at minimum) the following
          keys:
            - "log-likelihood_null"
            - "final_log_likelihood"
            - "utility_coefs"
            - "intercept_params"
            - "shape_params"
            - "nest_params"
            - "chosen_probs"
            - "long_probs"
            - "residuals"
            - "ind_chi_squareds"
            - "rho_squared"
            - "rho_bar_squared"
            - "final_gradient"
            - "final_hessian"
            - "fisher_info"
      """
      if not just_point:
          # Perform preliminary calculations
          log_likelihood_at_zero =\
              estimator.convenience_calc_log_likelihood(estimator.zero_vector)
  
          initial_log_likelihood =\
              estimator.convenience_calc_log_likelihood(init_values)
  
          if print_results:
              # Print the log-likelihood at zero
              null_msg = "Log-likelihood at zero: {:,.4f}"
              print(null_msg.format(log_likelihood_at_zero))
  
              # Print the log-likelihood at the starting values
              init_msg = "Initial Log-likelihood: {:,.4f}"
              print(init_msg.format(initial_log_likelihood))
              sys.stdout.flush()
  
      # Get the hessian fucntion for this estimation process
      hess_func = estimator.calc_neg_hessian if use_hessian else None
  
      # Estimate the actual parameters of the model
      start_time = time.time()
  
      results = minimize(estimator.calc_neg_log_likelihood_and_neg_gradient,
                         init_values,
                         method=method,
                         jac=True,
                         hess=hess_func,
                         tol=loss_tol,
                         options={'gtol': gradient_tol,
                                  "maxiter": maxiter},
                         **kwargs)
  
      if not just_point:
          if print_results:
              # Stop timing the estimation process and report the timing results
              end_time = time.time()
              elapsed_sec = (end_time - start_time)
              elapsed_min = elapsed_sec / 60.0
              if elapsed_min > 1.0:
                  msg = "Estimation Time for Point Estimation: {:.2f} minutes."
                  print(msg.format(elapsed_min))
              else:
                  msg = "Estimation Time for Point Estimation: {:.2f} seconds."
                  print(msg.format(elapsed_sec))
              print("Final log-likelihood: {:,.4f}".format(-1 * results["fun"]))
              sys.stdout.flush()
  
          # Store the log-likelihood at zero
          results["log_likelihood_null"] = log_likelihood_at_zero
  
          # Calculate and store the post-estimation results
          results = calc_and_store_post_estimation_results(results, estimator)
  
      return results