code: def assert_close(a, b, rtol=1e-07, atol=0, context=None):
      
      if isinstance(a, float) or isinstance(a, numpy.ndarray) and a.shape:
          # shortcut
          numpy.testing.assert_allclose(a, b, rtol, atol)
          return
      if isinstance(a, (str, bytes, int)):
          # another shortcut
          assert a == b, (a, b)
          return
      if hasattr(a, '_slots_'):  # record-like objects
          assert a._slots_ == b._slots_
          for x in a._slots_:
              assert_close(getattr(a, x), getattr(b, x), rtol, atol, x)
          return
      if hasattr(a, 'keys'):  # dict-like objects
          assert a.keys() == b.keys()
          for x in a:
              if x != '__geom__':
                  assert_close(a[x], b[x], rtol, atol, x)
          return
      if hasattr(a, '__dict__'):  # objects with an attribute dictionary
          assert_close(vars(a), vars(b), context=a)
          return
      if hasattr(a, '__iter__'):  # iterable objects
          xs, ys = list(a), list(b)
          assert len(xs) == len(ys), ('Lists of different lenghts: %d != %d'
                                      % (len(xs), len(ys)))
          for x, y in zip(xs, ys):
              assert_close(x, y, rtol, atol, x)
          return
      if a == b:  # last attempt to avoid raising the exception
          return
      ctx = '' if context is None else 'in context ' + repr(context)
      raise AssertionError('%r != %r %s' % (a, b, ctx))
docs: Compare for equality up to a given precision two composite objects
      which may contain floats. NB: if the objects are or contain generators,
      they are exhausted.
  
      :param a: an object
      :param b: another object
      :param rtol: relative tolerance
      :param atol: absolute tolerance