code: def assert_close(actual, expected, atol=1e-6, rtol=1e-6):
      msg = ActualExpected(actual, expected)
      if is_array(actual):
          assert is_array(expected), msg
      elif isinstance(actual, Tensor) and is_array(actual.data):
          assert isinstance(expected, Tensor) and is_array(
              expected.data
          ), msg
      elif (
          isinstance(actual, Contraction)
          and isinstance(actual.terms[0], Tensor)
          and is_array(actual.terms[0].data)
      ):
          assert isinstance(expected, Contraction) and is_array(
              expected.terms[0].data
          ), msg
      elif isinstance(actual, Contraction) and isinstance(
          actual.terms[0], Delta
      ):
          assert isinstance(expected, Contraction) and isinstance(
              expected.terms[0], Delta
          ), msg
      elif isinstance(actual, Gaussian):
          assert isinstance(expected, Gaussian)
      else:
          assert type(actual) is type(expected), msg
  
      if isinstance(actual, Funsor):
          assert isinstance(expected, Funsor), msg
          assert actual.inputs == expected.inputs, (
              actual.inputs,
              expected.inputs,
          )
          assert actual.output == expected.output, (
              actual.output,
              expected.output,
          )
  
      if isinstance(actual, (Number, Tensor)):
          assert_close(actual.data, expected.data, atol=atol, rtol=rtol)
      elif isinstance(actual, Delta):
          assert frozenset(n for n, p in actual.terms) == frozenset(
              n for n, p in expected.terms
          )
          actual = actual.align(tuple(n for n, p in expected.terms))
          for (actual_name, (actual_point, actual_log_density)), (
              expected_name,
              (expected_point, expected_log_density),
          ) in zip(actual.terms, expected.terms):
              assert actual_name == expected_name
              assert_close(
                  actual_point, expected_point, atol=atol, rtol=rtol
              )
              assert_close(
                  actual_log_density,
                  expected_log_density,
                  atol=atol,
                  rtol=rtol,
              )
      elif isinstance(actual, Gaussian):
          # Note white_vec and prec_sqrt are expected to agree only up to an
          # orthogonal factor, but precision and info_vec should agree exactly.
          assert_close(
              actual._info_vec, expected._info_vec, atol=atol, rtol=rtol
          )
          assert_close(
              actual._precision,
              expected._precision,
              atol=atol,
              rtol=rtol,
          )
      elif isinstance(actual, Contraction):
          assert actual.red_op == expected.red_op
          assert actual.bin_op == expected.bin_op
          assert actual.reduced_vars == expected.reduced_vars
          assert len(actual.terms) == len(expected.terms)
          for ta, te in zip(actual.terms, expected.terms):
              assert_close(ta, te, atol, rtol)
      elif type(actual).__name__ == "Tensor":
          assert get_backend() == "torch"
          import torch
  
          assert actual.dtype == expected.dtype, msg
          assert actual.shape == expected.shape, msg
          if actual.dtype in (torch.long, torch.uint8, torch.bool):
              assert (actual == expected).all(), msg
          else:
              eq = actual == expected
              if eq.all():
                  return
              if eq.any():
                  actual = actual[~eq]
                  expected = expected[~eq]
              diff = (actual.detach() - expected.detach()).abs()
              if rtol is not None:
                  assert (
                      diff / (atol + expected.detach().abs())
                  ).max() < rtol, msg
              elif atol is not None:
                  assert diff.max() < atol, msg
      elif is_array(actual):
          if get_backend() == "jax":
              import jax
  
              assert jax.numpy.result_type(
                  actual.dtype
              ) == jax.numpy.result_type(expected.dtype), msg
          else:
              assert actual.dtype == expected.dtype, msg
  
          assert actual.shape == expected.shape, msg
          if actual.dtype in (np.int32, np.int64, np.uint8, bool):
              assert (actual == expected).all(), msg
          else:
              actual, expected = np.asarray(actual), np.asarray(
                  expected
              )
              eq = actual == expected
              if eq.all():
                  return
              if eq.any():
                  actual = actual[~eq]
                  expected = expected[~eq]
              diff = abs(actual - expected)
              if rtol is not None:
                  assert (
                      diff / (atol + abs(expected))
                  ).max() < rtol, msg
              elif atol is not None:
                  assert diff.max() < atol, msg
      elif isinstance(actual, numbers.Number):
          if actual != expected:
              diff = abs(actual - expected)
              if rtol is not None:
                  assert diff < (atol + abs(expected)) * rtol, msg
              elif atol is not None:
                  assert diff < atol, msg
      elif isinstance(actual, dict):
          assert isinstance(expected, dict)
          assert set(actual) == set(expected)
          for k, actual_v in actual.items():
              assert_close(actual_v, expected[k], atol=atol, rtol=rtol)
      elif isinstance(actual, tuple):
          assert isinstance(expected, tuple)
          assert len(actual) == len(expected)
          for actual_v, expected_v in zip(actual, expected):
              assert_close(actual_v, expected_v, atol=atol, rtol=rtol)
      else:
          raise ValueError(
              "cannot compare objects of type {}".format(type(actual))
          )
  
imports: import contextlib
  import importlib
  import itertools
  import numbers
  import operator
  from collections import OrderedDict, namedtuple
  from functools import reduce
  import numpy
  import opt_einsum
  from multipledispatch import dispatch
  from multipledispatch.variadic import Variadic
  import funsor.ops
  from funsor.cnf import Contraction
  from funsor.delta import Delta
  from funsor.domains import Bint, Domain, Real
  from funsor.gaussian import Gaussian
  from funsor.tensor import Tensor
  from funsor.terms import Funsor, Number
  from funsor.util import get_backend
label_desc: Use image node_id 9 for calling a global function with example usage: assert_close(actual, expected, atol, rtol) without return types