def __call__(self, *args, **kwargs): if self.interface == "autograd": # HOTFIX: to maintain compatibility with core, here we treat # all inputs that do not explicitly specify `requires_grad=False` # as trainable. This should be removed at some point, forcing users # to specify `requires_grad=True` for trainable parameters. args = [ anp.array(a, requires_grad=True) if not hasattr(a, "requires_grad") else a for a in args ] # construct the tape self.construct(args, kwargs) # execute the tape res = self.qtape.execute(device=self.device) if isinstance(self.qfunc_output, Sequence): return res # HOTFIX: Output is a single measurement function. To maintain compatibility # with core, we squeeze all outputs. # Get the namespace associated with the return type res_type_namespace = res.__class__.__module__.split(".")[0] if res_type_namespace in ("pennylane", "autograd"): # For PennyLane and autograd we must branch, since # 'squeeze' does not exist in the top-level of the namespace return anp.squeeze(res) # Same for JAX if res_type_namespace == "jax": return __import__(res_type_namespace).numpy.squeeze(res) return __import__(res_type_namespace).squeeze(res)
def __call__(self, *args, **kwargs): if self.interface == "autograd": # HOTFIX: to maintain compatibility with core, here we treat # all inputs that do not explicitly specify `requires_grad=False` # as trainable. This should be removed at some point, forcing users # to specify `requires_grad=True` for trainable parameters. args = [ anp.array(a, requires_grad=True) if not hasattr(a, "requires_grad") else a for a in args ] # construct the tape self.construct(args, kwargs) if self._caching: # Every time the QNode is called, it creates a new tape. We want the tape cache to # persist over multiple tapes, so hence keep track of it as a QNode attribute and # load it into the new tape self.qtape._cache_execute = self._cache_execute # execute the tape res = self.qtape.execute(device=self.device) # HOTFIX: to maintain compatibility with core, we squeeze # all outputs. # Get the namespace associated with the return type res_type_namespace = res.__class__.__module__.split(".")[0] if res_type_namespace in ("pennylane", "autograd"): # For PennyLane and autograd we must branch, since # 'squeeze' does not exist in the top-level of the namespace return anp.squeeze(res) if self._caching: self._cache_execute = self.qtape._cache_execute return __import__(res_type_namespace).squeeze(res)
def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" if self.interface == "autograd": # HOTFIX: to maintain compatibility with core, here we treat # all inputs that do not explicitly specify `requires_grad=False` # as trainable. This should be removed at some point, forcing users # to specify `requires_grad=True` for trainable parameters. args = [ anp.array(a, requires_grad=True) if not hasattr(a, "requires_grad") else a for a in args ] self.qtape = self._tape() with self.qtape: self.qfunc_output = self.func(*args, **kwargs) if not isinstance(self.qfunc_output, Sequence): measurement_processes = (self.qfunc_output,) else: measurement_processes = self.qfunc_output if not all(isinstance(m, qml.tape.MeasurementProcess) for m in measurement_processes): raise qml.QuantumFunctionError( "A quantum function must return either a single measurement, " "or a nonempty sequence of measurements." ) state_returns = any([m.return_type is State for m in measurement_processes]) # apply the interface (if any) if self.diff_options["method"] != "backprop" and self.interface is not None: # pylint: disable=protected-access if state_returns and self.interface in ["torch", "tf"]: # The state is complex and we need to indicate this in the to_torch or to_tf # functions self.INTERFACE_MAP[self.interface](self, dtype=np.complex128) else: self.INTERFACE_MAP[self.interface](self) if not all(ret == m for ret, m in zip(measurement_processes, self.qtape.measurements)): raise qml.QuantumFunctionError( "All measurements must be returned in the order they are measured." ) for obj in self.qtape.operations + self.qtape.observables: if getattr(obj, "num_wires", None) is qml.operation.WiresEnum.AllWires: # check here only if enough wires if len(obj.wires) != self.device.num_wires: raise qml.QuantumFunctionError( "Operator {} must act on all wires".format(obj.name) ) # provide the jacobian options self.qtape.jacobian_options = self.diff_options # pylint: disable=protected-access obs_on_same_wire = len(self.qtape._obs_sharing_wires) > 0 ops_not_supported = any( isinstance(op, qml.tape.QuantumTape) # nested tapes must be expanded or not self.device.supports_operation(op.name) # unsupported ops must be expanded for op in self.qtape.operations ) # expand out the tape, if nested tapes are present, any operations are not supported on the # device, or multiple observables are measured on the same wire if ops_not_supported or obs_on_same_wire: self.qtape = self.qtape.expand( depth=self.max_expansion, stop_at=lambda obj: not isinstance(obj, qml.tape.QuantumTape) and self.device.supports_operation(obj.name), )