docs: def line_search(self,x,p,F,GradF,func,smax=np.inf,maxiter=40):
          """
          Finds steplength along search direction p that 
          satisfies the strong Wolfe conditions.
          
          Parameters
          ----------
          x : current point (ndarray)
          p : search direction (ndarray)
          F : function value at `x` (float)
          GradF : gradient of function at `x` (ndarray)
          func : function of `x` that returns function object with attributes `F` and `GradF` (function)
          smax : maximum allowed steplength (float)
             
          Returns
          -------
          s : stephlength that satisfies the Wolfe conditions (float).           
          """
          
          # Parameters of line search
          c1 = 1e-4
          c2 = 5e-1
  
          # Initialize lower bound, upper bound and step
          l = 0.
          if 1. < smax:
              s = 1.
          else:
              s = smax
          u = np.NaN        
  
          phi = F
          dphi = np.dot(GradF,p)
          
          # Check that p is descent direction
          if dphi >= 0:
              raise OptSolverError_BadSearchDir(self)
  
          # Bisection
          for i in range(0,maxiter):
              
              xsp = x+s*p
              
              fdata = func(xsp)
              phis = fdata.F
              dphis = np.dot(fdata.GradF,p)
              
              if phis > phi + c1*s*dphi:
                  u = s
              elif dphis > 0 and dphis > -c2*dphi:
                  u = s
              elif dphis < 0 and -dphis > -c2*dphi:
                  l = s
                  if s >= smax:
                      return s,fdata
              else:
                  return s,fdata
  
              if np.isnan(u):
                  s = np.min([2.*s,smax])
              else:
                  s = (l + u)/2.
  
          raise OptSolverError_LineSearch(self)