def newton_raphson_solve(
  fnc, init_guess, init_increment=0.01, tol=0.0001, slope_tol=1e-9,
  iter_max=8, fnc_params=(), bounds=None, verbose=False, return_last=False,
  return_converge_data=False):
  """Returns root for provided function using newton-raphson method.

  Returns None if tolerance is not met within number of iterations.

  Args:
    fnc: function to find root of
    init_guess: initial guess of root
    init_increment: relative change of init guess for initial increment
    tol: relative change of root for convergence
    iter_max: maximum number of iterations
    fnc_params: tuple of other params to pass into function
    bounds: tuple of limits for guesses
    verbose: prints convergence messages
  """
  x_new = None

  error_old = None
  error_new = None
  error_slope = None
  converge_data = []

  # Find error of initial guess
  x_old = init_guess
  # Get the first derivative
  error_old = fnc(x_old, *fnc_params)

  x_new = x_old + init_increment * init_guess
  error_new = fnc(x_new, *fnc_params)

  iteration = 0
  converged = False
  for iteration in range(iter_max):
    if x_new == x_old:
      error_slope = 0.
    else:
      error_slope = (error_new - error_old)/(x_new - x_old)
    if abs(error_slope) < slope_tol:
      if verbose:
        print('Did not converge. Slope not in tolerance on iteration %d' % iteration)
        print('Current slope is %0.3g, limit is %0.3g' % (error_slope, slope_tol))
      if not return_last:
        x_new = None
      break
    x_old = x_new
    error_old = error_new
    x_new = x_new - error_new / error_slope
    if bounds is not None:
      if x_new <= bounds[0]:
        x_new = bounds[0]
        if x_new == x_old:
          if verbose:
            print('Value of %0.4g is at lower bound of %0.4g' % (x_new, bounds[0]))
          converged = False
          break
      if x_new >= bounds[1]:
        x_new = bounds[1]
        if x_new == x_old:
          if verbose:
            print('Value of %0.4g is at upper bound of %0.4g' % (x_new, bounds[1]))
          converged = False
          break

    error_new = fnc(x_new, *fnc_params)
    if return_converge_data:
      converge_data.append((x_old, x_new, error_old, error_new, error_slope))

    if abs(error_new) < tol:
      converged = True
      if verbose == True:
        print('Converged to %0.4g in %d iterations. Tolerance is %0.4g' % (error_new, iteration, tol))
      break
  if not converged:
    if not return_last:
      x_new = None
    if verbose == True:
      print('Did not converge within tolerance of %f and iteration limit of %d' % (tol, iter_max))
      print('Current error is %0.4g' % error_new)
  if return_converge_data:
    return x_new, converge_data
  else:
    return x_new