code: def create_distance_op_from_fn(fn):
      scalarX = (
          pt.dscalar
          if pytensor.config.floatX == "float64"
          else pt.fscalar
      )
      vectorX = (
          pt.dvector
          if pytensor.config.floatX == "float64"
          else pt.fvector
      )
  
      # Check if callable returns TensorVariable with dummy inputs
      try:
          res = fn(scalarX(), vectorX(), vectorX())
          if isinstance(res, TensorVariable):
              return fn
      except Exception:
          pass
  
      # Otherwise, automatically wrap in PyTensor Op
      class Distance(Op):
          def make_node(self, epsilon, obs_data, sim_data):
              epsilon = pt.as_tensor_variable(epsilon)
              obs_data = pt.as_tensor_variable(obs_data)
              sim_data = pt.as_tensor_variable(sim_data)
              return Apply(
                  self, [epsilon, obs_data, sim_data], [vectorX()]
              )
  
          def perform(self, node, inputs, outputs):
              eps, obs_data, sim_data = inputs
              outputs[0][0] = np.atleast_1d(
                  fn(eps, obs_data, sim_data)
              ).astype(pytensor.config.floatX)
  
      return Distance()
  
imports: import logging
  import numpy
  import pytensor
  import pytensor.tensor
  from pytensor.graph.op import Apply, Op
  from pytensor.tensor.random.op import RandomVariable
  from pytensor.tensor.variable import TensorVariable
  from scipy.spatial import cKDTree
  from pymc.distributions.distribution import Distribution, _moment
  from pymc.logprob.abstract import _logprob
  from pymc.pytensorf import floatX
label_desc: Use image node_id 7 for calling a global function with example usage: create_distance_op_from_fn(fn) and returns: Distance, fn, Apply