code: def ctrl(op, control, control_values=None, work_wires=None):
      """Create a method that applies a controlled version of the provided op.
      :func:`~.qjit` compatible.
  
      .. note::
  
          When used with :func:`~.qjit`, this function only supports the Catalyst compiler.
          See :func:`catalyst.ctrl` for more details.
  
          Please see the Catalyst :doc:`quickstart guide <catalyst:dev/quick_start>`,
          as well as the :doc:`sharp bits and debugging tips <catalyst:dev/sharp_bits>`
          page for an overview of the differences between Catalyst and PennyLane.
  
      Args:
          op (function or :class:`~.operation.Operator`): A single operator or a function that applies pennylane operators.
          control (Wires): The control wire(s).
          control_values (bool or list[bool]): The value(s) the control wire(s) should take.
              Integers other than 0 or 1 will be treated as ``int(bool(x))``.
          work_wires (Any): Any auxiliary wires that can be used in the decomposition
  
      Returns:
          (function or :class:`~.operation.Operator`): If an Operator is provided, returns a Controlled version of the Operator.
          If a function is provided, returns a function with the same call signature that creates a controlled version of the
          provided function.
  
      .. seealso:: :class:`~.Controlled`.
  
      **Example**
  
      .. code-block:: python3
  
          @qml.qnode(qml.device('default.qubit', wires=range(4)))
          def circuit(x):
              qml.PauliX(2)
              qml.ctrl(qml.RX, (1,2,3), control_values=(0,1,0))(x, wires=0)
              return qml.expval(qml.PauliZ(0))
  
      >>> print(qml.draw(circuit)("x"))
      0: ────╭RX(x)─┤  <Z>
      1: ────├○─────┤
      2: ──X─├●─────┤
      3: ────╰○─────┤
      >>> x = np.array(1.2)
      >>> circuit(x)
      tensor(0.36235775, requires_grad=True)
      >>> qml.grad(circuit)(x)
      tensor(-0.93203909, requires_grad=True)
  
      :func:`~.ctrl` works on both callables like ``qml.RX`` or a quantum function
      and individual :class:`~.operation.Operator`'s.
  
      >>> qml.ctrl(qml.Hadamard(0), (1,2))
      Controlled(Hadamard(wires=[0]), control_wires=[1, 2])
  
      Controlled operations work with all other forms of operator math and simplification:
  
      >>> op = qml.ctrl(qml.RX(1.2, wires=0) ** 2 @ qml.RY(0.1, wires=0), control=1)
      >>> qml.simplify(qml.adjoint(op))
      Controlled(RY(12.466370614359173, wires=[0]) @ RX(10.166370614359172, wires=[0]), control_wires=[1])
  
      **Example with compiler**
  
      .. code-block:: python
  
          dev = qml.device("lightning.qubit", wires=2)
  
          @qml.qjit
          @qml.qnode(dev)
          def workflow(theta, w, cw):
              qml.Hadamard(wires=[0])
              qml.Hadamard(wires=[1])
  
              def func(arg):
                  qml.RX(theta, wires=arg)
  
              def cond_fn():
                  qml.RY(theta, wires=w)
  
              qml.ctrl(func, control=[cw])(w)
              qml.ctrl(qml.cond(theta > 0.0, cond_fn), control=[cw])()
              qml.ctrl(qml.RZ, control=[cw])(theta, wires=w)
              qml.ctrl(qml.RY(theta, wires=w), control=[cw])
              return qml.probs()
  
      >>> workflow(jnp.pi/4, 1, 0)
      array([0.25, 0.25, 0.03661165, 0.46338835])
      """
  
      if active_jit := compiler.active_compiler():
          available_eps = compiler.AvailableCompilers.names_entrypoints
          ops_loader = available_eps[active_jit]["ops"].load()
          return ops_loader.ctrl(
              op,
              control,
              control_values=control_values,
              work_wires=work_wires,
          )
  
      custom_controlled_ops = {
          (qml.PauliZ, 1): qml.CZ,
          (qml.PauliY, 1): qml.CY,
          (qml.PauliX, 1): qml.CNOT,
          (qml.PauliX, 2): qml.Toffoli,
      }
      control_values = (
          [control_values]
          if isinstance(control_values, (int, bool))
          else control_values
      )
      control = qml.wires.Wires(control)
      custom_key = (type(op), len(control))
  
      if custom_key in custom_controlled_ops and (
          control_values is None or all(control_values)
      ):
          qml.QueuingManager.remove(op)
          return custom_controlled_ops[custom_key](control + op.wires)
      if isinstance(op, qml.PauliX):
          qml.QueuingManager.remove(op)
          control_string = (
              None
              if control_values is None
              else "".join([str(int(v)) for v in control_values])
          )
          return qml.MultiControlledX(
              wires=control + op.wires,
              control_values=control_string,
              work_wires=work_wires,
          )
      if isinstance(op, Operator):
          return Controlled(
              op,
              control_wires=control,
              control_values=control_values,
              work_wires=work_wires,
          )
      if not callable(op):
          raise ValueError(
              f"The object {op} of type {type(op)} is not an Operator or callable. "
              "This error might occur if you apply ctrl to a list "
              "of operations instead of a function or Operator."
          )
  
      @wraps(op)
      def wrapper(*args, **kwargs):
          qscript = qml.tape.make_qscript(op)(*args, **kwargs)
  
          # flip control_values == 0 wires here, so we don't have to do it for each individual op.
          flip_control_on_zero = (len(qscript) > 1) and (
              control_values is not None
          )
          op_control_values = (
              None if flip_control_on_zero else control_values
          )
          if flip_control_on_zero:
              _ = [
                  qml.PauliX(w)
                  for w, val in zip(control, control_values)
                  if not val
              ]
  
          _ = [
              ctrl(
                  op,
                  control=control,
                  control_values=op_control_values,
                  work_wires=work_wires,
              )
              for op in qscript.operations
          ]
  
          if flip_control_on_zero:
              _ = [
                  qml.PauliX(w)
                  for w, val in zip(control, control_values)
                  if not val
              ]
  
          if qml.QueuingManager.recording():
              _ = [qml.apply(m) for m in qscript.measurements]
  
          return qscript.measurements
  
      return wrapper
  
imports: import warnings
  from copy import copy
  from functools import wraps
  from inspect import signature
  from typing import List
  import numpy
  from scipy import sparse
  import pennylane
  from pennylane import operation
  from pennylane import math
  from pennylane.operation import Operator
  from pennylane.wires import Wires
  from pennylane.compiler import compiler
  from .symbolicop import SymbolicOp
  from .controlled_decompositions import ctrl_decomp_bisect, ctrl_decomp_zyz
label_desc: Use image node_id 1 for calling a global function with example usage: ctrl(op, control, control_values, work_wires) and returns: wrapper, ops_loader, custom_controlled_ops, qml, Controlled, qscript