docs: def search(fcn, x0, incr=0, fac=1.1, maxit=100, analyzer=None):
      """ Search for and bracket root of one-dimensional function ``fcn(x)``.
  
      This method searches for an interval in ``x`` that brackets 
      a root of ``fcn(x)=0``. It examines points ::
  
          x[j + 1] = fac * x[j] + incr
  
      where ``x[0]=x0`` and ``j=0...maxit-1``, looking for a pair 
      of successive points where ``fcn(x[j])`` changes sign. These
      points bracket a root (assuming the function is continuous),
      providing a coarse estimate of the root. That estimate can
      be refined using :meth:`root.refine`.
  
      Example:
          The following code seeks to bracket the first zero of
          ``sin(x)`` with ``x>0.1``::
  
              >>> import math
              >>> import gvar as gv
              >>> interval = gv.root.search(math.sin, 0.1)
              >>> print(interval)
              (3.0912680532870755, 3.4003948586157833)
  
          The resulting interval correctly brackets the root 
          at ``pi``.
  
      Args:
          fcn: One dimenionsal function whose root is sought.
          x0 (float): Starting point for search.
          incr (float, optional): Increment used for linear searches. Default 
              value is 0.
          fac (float, optional): Rescaling factor for exponential searches. 
              Default value is 1.1.
          maxit (int, optional): Maximum number of steps allowed for search. An 
              exception is raised if a root is not found in time. Default
              value is 100.
          analyzer: Optional function ``f(x, fcn(x))`` that is called
              for each point ``x`` that is examined. This can be used,
              for example, to monitor the search while debugging. 
              Default is ``None``.
  
      Returns:
          Tuple ``(a, b)`` where ``fcn(a) * fcn(b) <= 0``, which implies
          that a root occurs between ``a`` and ``b`` (provided the 
          function is continuous). The tuple has extra attributes 
          that provide additional information about the search:
  
          - **nit** --- Number of iterations used to find interval ``(a,b)``.
          - **fcnval** --- Tuple containing the function values at ``(a,b)``.
  
      Raises:
          RuntimeError: If unable to find a root in ``maxit`` steps.
      """
      # custom return class for result
      class ans(tuple):
          def __new__(cls, interval, nit, fcnval):
              return tuple.__new__(cls, interval)
          def __init__(self, interval, nit, fcnval):
              self.interval = interval
              self.nit = nit 
              self.fcnval = fcnval
      x = x0
      f = fcn(x)
      for nit in range(maxit):
          xo, fo = x, f
          x = xo * fac + incr
          f = fcn(x)
          if analyzer!=None:
              analyzer(x, f)
          if f*fo<=0:
              if numpy.fabs(fo)<=numpy.fabs(f):
                  return ans((xo, x), nit=nit + 1, fcnval=(fo, f))
              else:
                  return ans((x, xo), nit=nit+1, fcnval=(f, fo))
      raise RuntimeError("unable to bracket root")