def test_step_and_cost_with_grad_fn_grouped_input(self, tol): """Test that the correct cost and update is returned via the step_and_cost method for the QNG optimizer when providing an explicit grad_fn. Using a circuit with a single input containing all parameters.""" dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) return qml.expval(qml.PauliZ(0)) var = np.array([0.011, 0.012]) opt = qml.QNGOptimizer(stepsize=0.01) # With autograd gradient function grad_fn = qml.grad(circuit) step1, cost1 = opt.step_and_cost(circuit, var, grad_fn=grad_fn) step2 = opt.step(circuit, var, grad_fn=grad_fn) # With more custom gradient function, forward has to be computed explicitly. grad_fn = lambda param: np.array(qml.grad(circuit)(param)) step3, cost2 = opt.step_and_cost(circuit, var, grad_fn=grad_fn) step4 = opt.step(circuit, var, grad_fn=grad_fn) expected_step = var - opt.stepsize * 4 * grad_fn(var) expected_cost = circuit(var) for step in [step1, step2, step3, step3]: assert np.allclose(step, expected_step) assert np.isclose(cost1, expected_cost) assert np.isclose(cost2, expected_cost)
def test_var_two_wires_with_parameters(self, qubit_device_2_wires, tol, name, input, expected_output, par): """Tests that variances are properly calculated for two-wire observables with parameters.""" qubit_device_2_wires._state = np.array(input) res = qubit_device_2_wires.var(name, wires=[0, 1], par=par) assert np.isclose(res, expected_output, atol=tol, rtol=0)
def test_four_term_case(self): """Tests the parameter shift rules for `CommutingEvolution` equal the finite difference result for a four term shift rule case.""" n_wires = 2 dev = qml.device("default.qubit", wires=n_wires) coeffs = [1, -1] obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] hamiltonian = qml.Hamiltonian(coeffs, obs) frequencies = (2, 4) @qml.qnode(dev) def circuit(time): qml.PauliX(0) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(0)) x_vals = [ np.array(x, requires_grad=True) for x in np.linspace(-np.pi, np.pi, num=10) ] grads_finite_diff = [ qml.gradients.finite_diff(circuit)(x) for x in x_vals ] grads_param_shift = [ qml.gradients.param_shift(circuit)(x) for x in x_vals ] assert all(np.isclose(grads_finite_diff, grads_param_shift, atol=1e-4))
def test_two_term_case(self): """Tests the parameter shift rules for `CommutingEvolution` equal the finite difference result for a two term shift rule case.""" n_wires = 1 dev = qml.device("default.qubit", wires=n_wires) hamiltonian = qml.Hamiltonian([1], [qml.PauliX(0)]) frequencies = (2, ) @qml.qnode(dev) def circuit(time): qml.PauliX(0) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(0)) x_vals = np.linspace(-np.pi, np.pi, num=10) grads_finite_diff = [ qml.gradients.finite_diff(circuit)(x) for x in x_vals ] grads_param_shift = [ qml.gradients.param_shift(circuit)(x) for x in x_vals ] assert all(np.isclose(grads_finite_diff, grads_param_shift, atol=1e-4))
def test_adjoint(): """Tests the CommutingEvolution.adjoint method provides the correct adjoint operation.""" n_wires = 2 dev1 = qml.device("default.qubit", wires=n_wires) dev2 = qml.device("default.qubit", wires=n_wires) obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] coeffs = [1, -1] hamiltonian = qml.Hamiltonian(coeffs, obs) frequencies = (2, ) @qml.qnode(dev1) def adjoint_evolution_circuit(time): for i in range(n_wires): qml.Hadamard(i) qml.adjoint(qml.CommutingEvolution)(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(1)) @qml.qnode(dev2) def evolution_circuit(time): for i in range(n_wires): qml.Hadamard(i) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(1)) evolution_circuit(0.13) adjoint_evolution_circuit(-0.13) assert all(np.isclose(dev1.state, dev2.state))
def test_differentiable_hamiltonian(self): """Tests correct gradients are produced when the Hamiltonian is differentiable.""" n_wires = 2 dev = qml.device("default.qubit", wires=n_wires) obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] diff_coeffs = np.array([1.0, -1.0], requires_grad=True) frequencies = (2, 4) def parameterized_hamiltonian(coeffs): return qml.Hamiltonian(coeffs, obs) @qml.qnode(dev) def circuit(time, coeffs): qml.PauliX(0) qml.CommutingEvolution(parameterized_hamiltonian(coeffs), time, frequencies) return qml.expval(qml.PauliZ(0)) x_vals = [ np.array(x, requires_grad=True) for x in np.linspace(-np.pi, np.pi, num=10) ] grads_finite_diff = [ np.hstack(qml.gradients.finite_diff(circuit)(x, diff_coeffs)) for x in x_vals ] grads_param_shift = [ np.hstack(qml.gradients.param_shift(circuit)(x, diff_coeffs)) for x in x_vals ] assert np.isclose(grads_finite_diff, grads_param_shift, atol=1e-6).all()
def test_single_step(self, qnode, param, nums_frequency, spectra, substep_optimizer, substep_kwargs): """Test executing a single step of the RotosolveOptimizer on a QNode.""" param = tuple(np.array(p, requires_grad=True) for p in param) opt = RotosolveOptimizer(substep_optimizer, substep_kwargs) repack_param = len(param) == 1 new_param_step = opt.step( qnode, *param, nums_frequency=nums_frequency, spectra=spectra, ) if repack_param: new_param_step = (new_param_step, ) assert (np.isscalar(new_param_step) and np.isscalar(param)) or len(new_param_step) == len(param) new_param_step_and_cost, old_cost = opt.step_and_cost( qnode, *param, nums_frequency=nums_frequency, spectra=spectra, ) if repack_param: new_param_step_and_cost = (new_param_step_and_cost, ) assert np.allclose( np.fromiter(_flatten(new_param_step_and_cost), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), ) assert np.isclose(qnode(*param), old_cost)
def test_full_output(self, fun, x_min, param, num_freq, optimizer, optimizer_kwargs): """Tests the ``full_output`` feature of Rotosolve, delivering intermediate cost function values at the univariate optimization substeps.""" opt = RotosolveOptimizer() _, y_output_step = opt.step( fun, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, full_output=True, ) new_param, old_cost, y_output_step_and_cost = opt.step_and_cost( fun, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, full_output=True, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step = (new_param,) expected_intermediate_x = successive_params(param, new_param) expected_y_output = [fun(*par) for par in expected_intermediate_x[1:]] assert np.allclose(y_output_step, expected_y_output) assert np.allclose(y_output_step_and_cost, expected_y_output) assert np.isclose(old_cost, fun(*expected_intermediate_x[0]))
def test_single_step(self, qnode, param, num_freq, optimizer, optimizer_kwargs): opt = RotosolveOptimizer() repack_param = len(param) == 1 new_param_step = opt.step( qnode, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, ) if repack_param: new_param_step = (new_param_step,) assert (np.isscalar(new_param_step) and np.isscalar(param)) or len(new_param_step) == len( param ) new_param_step_and_cost, old_cost = opt.step_and_cost( qnode, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, ) if repack_param: new_param_step_and_cost = (new_param_step_and_cost,) assert np.allclose( np.fromiter(_flatten(new_param_step_and_cost), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), ) assert np.isclose(qnode(*param), old_cost)
def test_var_single_wire_with_parameters(self, qubit_device_1_wire, tol, name, input, expected_output, par): """Tests that expectation values are properly calculated for single-wire observables with parameters.""" qubit_device_1_wire._state = np.array(input) res = qubit_device_1_wire.var(name, wires=[0], par=par) assert np.isclose(res, expected_output, atol=tol, rtol=0)
def test_single_step_convergence(self, fun, x_min, param, nums_freq, exp_num_calls, substep_optimizer, substep_kwargs): """Tests convergence for easy classical functions in a single Rotosolve step. Includes testing of the parameter output shape and the old cost when using step_and_cost.""" opt = RotosolveOptimizer(substep_optimizer, substep_kwargs) # Make only the first argument trainable param = (np.array(param[0], requires_grad=True), ) + param[1:] # Only one argument is marked as trainable -> All other arguments have to stay fixed new_param_step = opt.step( fun, *param, nums_frequency=nums_freq, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step = (new_param_step, ) assert all( np.allclose(p, new_p) for p, new_p in zip(param[1:], new_param_step[1:])) # With trainable parameters, training should happen param = tuple(np.array(p, requires_grad=True) for p in param) new_param_step = opt.step( fun, *param, nums_frequency=nums_freq, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step = (new_param_step, ) assert len(x_min) == len(new_param_step) assert np.allclose( np.fromiter(_flatten(x_min), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) # Now with step_and_cost and trainable params new_param_step_and_cost, old_cost = opt.step_and_cost( fun, *param, nums_frequency=nums_freq, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step_and_cost = (new_param_step_and_cost, ) assert len(x_min) == len(new_param_step_and_cost) assert np.allclose( np.fromiter(_flatten(new_param_step_and_cost), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) assert np.isclose(old_cost, fun(*param))
def test_qubit_identity(self, tensornet_device_1_wire, tol): """Test that the tensor network plugin provides correct result for the Identity expectation""" p = 0.543 @qml.qnode(tensornet_device_1_wire) def circuit(x): """Test quantum function""" qml.RX(x, wires=0) return qml.expval(qml.Identity(0)) assert np.isclose(circuit(p), 1, atol=tol, rtol=0)
def test_supported_observable_two_wires_with_parameters(self, tensornet_device_2_wires, tol, name, state, expected_output, par): """Tests supported observables on two wires with parameters.""" obs = getattr(qml.ops, name) assert tensornet_device_2_wires.supports_observable(name) @qml.qnode(tensornet_device_2_wires) def circuit(): qml.QubitStateVector(np.array(state), wires=[0, 1]) return qml.expval(obs(*par, wires=[0, 1])) assert np.isclose(circuit(), expected_output, atol=tol, rtol=0)
def test_supported_observable_single_wire_no_parameters(self, tensornet_device_1_wire, tol, name, state, expected_output): """Tests supported observables on single wires without parameters.""" obs = getattr(qml.ops, name) assert tensornet_device_1_wire.supports_observable(name) @qml.qnode(tensornet_device_1_wire) def circuit(): qml.QubitStateVector(np.array(state), wires=[0]) return qml.expval(obs(wires=[0])) assert np.isclose(circuit(), expected_output, atol=tol, rtol=0)
def test_supported_gate_single_wire_with_parameters(self, tensornet_device_1_wire, tol, name, par, expected_output): """Tests supported gates that act on a single wire that are parameterized""" op = getattr(qml.ops, name) assert tensornet_device_1_wire.supports_operation(name) @qml.qnode(tensornet_device_1_wire) def circuit(): op(*par, wires=0) return qml.expval(qml.PauliZ(0)) assert np.isclose(circuit(), expected_output, atol=tol, rtol=0)
def test_qubit_circuit(self, tensornet_device_1_wire, tol): """Test that the tensor network plugin provides correct result for a simple circuit""" p = 0.543 @qml.qnode(tensornet_device_1_wire) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliY(0)) expected = -np.sin(p) assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_random_layer_randomwires(self, n_subsystems): """Test that random_layer() picks random wires.""" n_rots = 500 weights = np.random.randn(n_rots) with qml.utils.OperationRecorder() as rec: random_layer(weights=weights, wires=range(n_subsystems), ratio_imprim=0.3, imprimitive=qml.CNOT, rotations=[RX, RY, RZ], seed=42) wires = [q._wires for q in rec.queue] wires_flat = [item for w in wires for item in w] mean_wire = np.mean(wires_flat) assert np.isclose(mean_wire, (n_subsystems - 1) / 2, atol=0.05)
def test_random_layer_ratio_imprimitive(self, ratio): """Test that random_layer() has the right ratio of imprimitive gates.""" n_rots = 500 n_wires = 2 impr = CNOT weights = np.random.randn(n_rots) with qml.utils.OperationRecorder() as rec: random_layer(weights=weights, wires=range(n_wires), ratio_imprim=ratio, imprimitive=CNOT, rotations=[RX, RY, RZ], seed=42) types = [type(q) for q in rec.queue] ratio_impr = types.count(impr) / len(types) assert np.isclose(ratio_impr, ratio, atol=0.05)
def test_qubit_circuit(self, tol): """Test that the device provides the correct result for a simple circuit.""" p = np.array(0.543) dev = qml.device("default.qubit.autograd", wires=1) @qml.qnode(dev, interface="autograd") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliY(0)) expected = -np.sin(p) assert circuit.diff_options["method"] == "backprop" assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def simplify(self): r"""Simplifies the Hamiltonian by combining like-terms. **Example** >>> ops = [qml.PauliY(2), qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0)] >>> H = qml.Hamiltonian([1, 1, -2], ops) >>> H.simplify() >>> print(H) (-1) [X0] + (1) [Y2] .. warning:: Calling this method will reset ``grouping_indices`` to None, since the observables it refers to are updated. """ data = [] ops = [] for i in range(len(self.ops)): # pylint: disable=consider-using-enumerate op = self.ops[i] c = self.coeffs[i] op = op if isinstance(op, Tensor) else Tensor(op) ind = None for j, o in enumerate(ops): if op.compare(o): ind = j break if ind is not None: data[ind] += c if np.isclose(qml.math.toarray(data[ind]), np.array(0.0)): del data[ind] del ops[ind] else: ops.append(op.prune()) data.append(c) self._coeffs = qml.math.stack(data) if data else [] self.data = data self._ops = ops # reset grouping, since the indices refer to the old observables and coefficients self._grouping_indices = None
def test_random_layer_randomwires(self, n_subsystems): """Test that pennylane.templates.layers._random_layer() picks random wires.""" np.random.seed(12) n_rots = 500 dev = qml.device('default.qubit', wires=n_subsystems) weights = np.random.randn(n_rots) def circuit(weights): _random_layer(weights=weights, wires=range(n_subsystems)) return qml.expval(qml.PauliZ(0)) qnode = qml.QNode(circuit, dev) qnode(weights) queue = qnode.queue wires = [q._wires for q in queue] wires_flat = [item for w in wires for item in w] mean_wire = np.mean(wires_flat) assert np.isclose(mean_wire, (n_subsystems - 1) / 2, atol=0.05)
def test_single_step_convergence( self, fun, x_min, param, num_freq, optimizer, optimizer_kwargs ): """Tests convergence for easy classical functions in a single Rotosolve step. Includes testing of the parameter output shape and the old cost when using step_and_cost.""" opt = RotosolveOptimizer() new_param_step = opt.step( fun, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step = (new_param_step,) assert len(x_min) == len(new_param_step) assert np.allclose( np.fromiter(_flatten(x_min), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) new_param_step_and_cost, old_cost = opt.step_and_cost( fun, *param, num_freqs=num_freq, optimizer=optimizer, optimizer_kwargs=optimizer_kwargs, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step_and_cost = (new_param_step_and_cost,) assert len(x_min) == len(new_param_step_and_cost) assert np.allclose( np.fromiter(_flatten(new_param_step_and_cost), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) assert np.isclose(old_cost, fun(*param))
def test_random_layer_imprimitive(self, ratio): """Test that pennylane.templates.layers.RandomLayer() has the right ratio of imprimitive gates.""" np.random.seed(12) n_rots = 500 n_wires = 2 impr = CNOT dev = qml.device('default.qubit', wires=n_wires) weights = np.random.randn(n_rots) def circuit(weights): RandomLayer(weights=weights, wires=range(n_wires), ratio_imprim=ratio, impr=CNOT) return qml.expval(qml.PauliZ(0)) qnode = qml.QNode(circuit, dev) qnode(weights) queue = qnode.queue types = [type(q) for q in queue] ratio_impr = types.count(impr) / len(types) assert np.isclose(ratio_impr, ratio, atol=0.05)
def test_nonzero_shots(self, tol): """Test that the default qubit plugin provides correct result for high shot number""" shots = 10 ** 5 dev = qml.device("default.qubit", wires=1, shots=shots) p = 0.543 @qml.qnode(dev) def circuit(x): """Test quantum function""" qml.RX(x, wires=0) return qml.expval(qml.PauliY(0)) runs = [] for _ in range(100): runs.append(circuit(p)) assert np.isclose(np.mean(runs), -np.sin(p), atol=tol, rtol=0)
def test_state_preparation_fidelity(self, tol, qubit_device_3_wires, state_vector, wires, target_state): """Tests that the template MottonenStatePreparation integrates correctly with PennyLane and produces states with correct fidelity.""" @qml.qnode(qubit_device_3_wires) def circuit(): MottonenStatePreparation(state_vector, wires) return qml.expval(qml.PauliZ(0)), qml.expval( qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) circuit() state = circuit.device.state.ravel() fidelity = abs(np.vdot(state, target_state))**2 # We test for fidelity here, because the vector themselves will hardly match # due to imperfect state preparation assert np.isclose(fidelity, 1, atol=tol, rtol=0)
def test_multi_qub_readout_correction(self): """Test the QPU plugin with readout errors and correction""" device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device("forest.qpu", device=device, load_qc=False, shots=10_000, readout_error=[0.9, 0.75], symmetrize_readout='exhaustive', calibrate_readout='plus-eig', parametric_compilation=False) @qml.qnode(dev_qpu) def circuit(): qml.RY(np.pi / 2, wires=0) qml.RY(np.pi / 3, wires=1) return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) result = circuit() assert np.isclose(result, 0.5, atol=3e-2)
def test_single_step(self, fun, x_min, param, num_freq): """Tests convergence for easy classical functions in a single Rotosolve step with some arguments deactivated for training. Includes testing of the parameter output shape and the old cost when using step_and_cost.""" substep_optimizer = "brute" substep_kwargs = None opt = RotosolveOptimizer(substep_optimizer, substep_kwargs) new_param_step = opt.step( fun, *param, nums_frequency=num_freq, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step = (new_param_step, ) assert len(x_min) == len(new_param_step) assert np.allclose( np.fromiter(_flatten(x_min), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) new_param_step_and_cost, old_cost = opt.step_and_cost( fun, *param, nums_frequency=num_freq, ) # The following accounts for the unpacking functionality for length-1 param if len(param) == 1: new_param_step_and_cost = (new_param_step_and_cost, ) assert len(x_min) == len(new_param_step_and_cost) assert np.allclose( np.fromiter(_flatten(new_param_step_and_cost), dtype=float), np.fromiter(_flatten(new_param_step), dtype=float), atol=1e-5, ) assert np.isclose(old_cost, fun(*param))
def test_random_layer_randomwires(self, n_subsystems): """Test that _random_layer() picks random wires.""" n_rots = 500 dev = qml.device('default.qubit', wires=n_subsystems) weights = np.random.randn(n_rots) def circuit(weights): _random_layer(weights=weights, wires=range(n_subsystems), ratio_imprim=0.3, imprimitive=qml.CNOT, rotations=[RX, RY, RZ], seed=42) return qml.expval(qml.PauliZ(0)) qnode = qml.QNode(circuit, dev) qnode(weights) queue = qnode.queue wires = [q._wires for q in queue] wires_flat = [item for w in wires for item in w] mean_wire = np.mean(wires_flat) assert np.isclose(mean_wire, (n_subsystems - 1) / 2, atol=0.05)
def test_multi_qub_no_readout_errors(self): """Test the QPU plugin with no readout errors or correction""" device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "forest.qpu", device=device, load_qc=False, symmetrize_readout=None, calibrate_readout=None, ) @qml.qnode(dev_qpu) def circuit(): qml.RY(np.pi / 2, wires=0) qml.RY(np.pi / 3, wires=1) return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) num_expts = 50 result = 0.0 for _ in range(num_expts): result += circuit() result /= num_expts assert np.isclose(result, 0.5, atol=2e-2)
def __init__( self, ansatz, hamiltonian, device, interface="autograd", diff_method="best", optimize=False, **kwargs, ): if kwargs.get("measure", "expval") != "expval": raise ValueError( "ExpvalCost can only be used to construct sums of expectation values." ) coeffs, observables = hamiltonian.terms self.hamiltonian = hamiltonian """Hamiltonian: the input Hamiltonian.""" self.qnodes = None """QNodeCollection: The QNodes to be evaluated. Each QNode corresponds to the expectation value of each observable term after applying the circuit ansatz.""" self._multiple_devices = isinstance(device, Sequence) """Bool: Records if multiple devices are input""" if np.isclose(qml.math.toarray(qml.math.count_nonzero(coeffs)), 0): self.cost_fn = lambda *args, **kwargs: np.array(0) return self._optimize = optimize self.qnodes = qml.map(ansatz, observables, device, interface=interface, diff_method=diff_method, **kwargs) if self._optimize: if self._multiple_devices: raise ValueError( "Using multiple devices is not supported when optimize=True" ) obs_groupings, coeffs_groupings = qml.grouping.group_observables( observables, coeffs) d = device[0] if self._multiple_devices else device w = d.wires.tolist() @qml.qnode(device, interface=interface, diff_method=diff_method, **kwargs) def circuit(*qnode_args, obs, **qnode_kwargs): """Converting ansatz into a full circuit including measurements""" ansatz(*qnode_args, wires=w, **qnode_kwargs) return [qml.expval(o) for o in obs] def cost_fn(*qnode_args, **qnode_kwargs): """Combine results from grouped QNode executions with grouped coefficients""" if device.shot_vector: shots_batch = 0 for i in device.shot_vector: shots_batch += i[1] total = [0] * shots_batch for o, c in zip(obs_groupings, coeffs_groupings): res = circuit(*qnode_args, obs=o, **qnode_kwargs) for i, batch_res in enumerate(res): total[i] += sum(batch_res * c) else: total = 0 for o, c in zip(obs_groupings, coeffs_groupings): res = circuit(*qnode_args, obs=o, **qnode_kwargs) total += sum([r * c_ for r, c_ in zip(res, c)]) return total self.cost_fn = cost_fn else: self.cost_fn = qml.dot(coeffs, self.qnodes)