def sgd(loss, initial_parameters, args=(), options=None, compile=False): """Stochastic Gradient Descent (SGD) optimizer using Tensorflow backpropagation. See `tf.keras.Optimizers <https://www.tensorflow.org/api_docs/python/tf/keras/optimizers>`_ for a list of the available optimizers. Args: loss (callable): Loss as a function of variational parameters to be optimized. initial_parameters (np.ndarray): Initial guess for the variational parameters. args (tuple): optional arguments for the loss function. options (dict): Dictionary with options for the SGD optimizer. Supports the following keys: - ``'optimizer'`` (str, default: ``'Adagrad'``): Name of optimizer. - ``'learning_rate'`` (float, default: ``'1e-3'``): Learning rate. - ``'nepochs'`` (int, default: ``1e6``): Number of epochs for optimization. - ``'nmessage'`` (int, default: ``1e3``): Every how many epochs to print a message of the loss function. """ from qibo import K from qibo.config import log, raise_error if not K.supports_gradients: raise_error(RuntimeError, "SGD optimizer requires Tensorflow backend.") sgd_options = { "nepochs": 1000000, "nmessage": 1000, "optimizer": "Adagrad", "learning_rate": 0.001 } if options is not None: sgd_options.update(options) # proceed with the training vparams = K.optimization.Variable(initial_parameters) optimizer = getattr( K.optimization.optimizers, sgd_options["optimizer"])(learning_rate=sgd_options["learning_rate"]) def opt_step(): with K.optimization.GradientTape() as tape: l = loss(vparams, *args) grads = tape.gradient(l, [vparams]) optimizer.apply_gradients(zip(grads, [vparams])) return l if compile: loss = K.compile(loss) opt_step = K.compile(opt_step) for e in range(sgd_options["nepochs"]): l = opt_step() if e % sgd_options["nmessage"] == 1: log.info('ite %d : loss %f', e, l.numpy()) return loss(vparams, *args).numpy(), vparams.numpy(), sgd_options
def test_apply_twoqubit_gate_controlled(nqubits, targets, controls, compile, einsum_str): """Check ``K.op.apply_twoqubit_gate`` for random gates.""" state = random_complex((2**nqubits, )) gate = random_complex((4, 4)) gatenp = gate.numpy().reshape(4 * (2, )) target_state = state.numpy().reshape(nqubits * (2, )) slicer = nqubits * [slice(None)] for c in controls: slicer[c] = 1 slicer = tuple(slicer) target_state[slicer] = np.einsum(einsum_str, target_state[slicer], gatenp) target_state = target_state.ravel() def apply_operator(state): qubits = qubits_tensor(nqubits, targets, controls) return K.op.apply_two_qubit_gate(state, gate, qubits, nqubits, *targets, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy())
def test_apply_zpow_gate(nqubits, target, controls, compile): """Check ``apply_zpow`` (including CZPow case).""" import itertools phase = np.exp(1j * 0.1234) qubits = controls[:] qubits.append(target) qubits.sort() matrix = np.ones(2**nqubits, dtype=np.complex128) for i, conf in enumerate(itertools.product([0, 1], repeat=nqubits)): if np.array(conf)[qubits].prod(): matrix[i] = phase state = random_complex((2**nqubits, )) target_state = np.diag(matrix).dot(state.numpy()) def apply_operator(state): qubits = qubits_tensor(nqubits, [target], controls) return K.op.apply_z_pow(state, phase, qubits, nqubits, target, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy())
def test_apply_fsim(nqubits, targets, controls, compile, einsum_str): """Check ``K.op.apply_twoqubit_gate`` for random gates.""" state = random_complex((2**nqubits, )) rotation = random_complex((2, 2)) phase = random_complex((1, )) target_state = state.numpy().reshape(nqubits * (2, )) gatenp = np.eye(4, dtype=target_state.dtype) gatenp[1:3, 1:3] = rotation.numpy() gatenp[3, 3] = phase.numpy()[0] gatenp = gatenp.reshape(4 * (2, )) slicer = nqubits * [slice(None)] for c in controls: slicer[c] = 1 slicer = tuple(slicer) target_state[slicer] = np.einsum(einsum_str, target_state[slicer], gatenp) target_state = target_state.ravel() gate = K.concatenate([K.reshape(rotation, (4, )), phase], axis=0) def apply_operator(state): qubits = qubits_tensor(nqubits, targets, controls) return K.op.apply_fsim(state, gate, qubits, nqubits, *targets, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy())
def test_apply_swap_general(nqubits, targets, controls, compile): """Check ``apply_swap`` for more general cases.""" state = random_complex((2**nqubits, )) target0, target1 = targets for q in controls: if q < targets[0]: target0 -= 1 if q < targets[1]: target1 -= 1 target_state = state.numpy().reshape(nqubits * (2, )) order = list(range(nqubits - len(controls))) order[target0], order[target1] = target1, target0 slicer = tuple(1 if q in controls else slice(None) for q in range(nqubits)) reduced_state = target_state[slicer] reduced_state = np.transpose(reduced_state, order) target_state[slicer] = reduced_state def apply_operator(state): qubits = qubits_tensor(nqubits, targets, controls) return K.op.apply_swap(state, qubits, nqubits, *targets, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state.ravel(), state.numpy())
def test_circuit_vs_gate_execution(backend, compile): """Check consistency between executing circuit and stand alone gates.""" from qibo import K theta = 0.1234 target_c = Circuit(2) target_c.add(gates.X(0)) target_c.add(gates.X(1)) target_c.add(gates.CU1(0, 1, theta)) target_result = target_c() # custom circuit def custom_circuit(initial_state, theta): l1 = gates.X(0)(initial_state) l2 = gates.X(1)(l1) o = gates.CU1(0, 1, theta)(l2) return o initial_state = target_c.get_initial_state() if compile: c = K.compile(custom_circuit) else: c = custom_circuit result = c(initial_state, theta) K.assert_allclose(result, target_result)
def test_circuit_vs_gate_execution(backend, compile): """Check consistency between executing circuit and stand alone gates.""" from qibo import K original_backend = qibo.get_backend() qibo.set_backend(backend) theta = 0.1234 target_c = Circuit(2) target_c.add(gates.X(0)) target_c.add(gates.X(1)) target_c.add(gates.CU1(0, 1, theta)) target_result = target_c() # custom circuit def custom_circuit(initial_state, theta): l1 = gates.X(0)(initial_state) l2 = gates.X(1)(l1) o = gates.CU1(0, 1, theta)(l2) return o initial_state = target_c.get_initial_state() if compile: c = K.compile(custom_circuit) else: c = custom_circuit if backend == "custom" and compile: with pytest.raises(NotImplementedError): result = c(initial_state, theta) else: result = c(initial_state, theta) np.testing.assert_allclose(result, target_result) qibo.set_backend(original_backend)
def minimize(self, initial_state, method='Powell', jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None, compile=False, processes=None): """Search for parameters which minimizes the hamiltonian expectation. Args: initial_state (array): a initial guess for the parameters of the variational circuit. method (str): the desired minimization method. See :meth:`qibo.optimizers.optimize` for available optimization methods. jac (dict): Method for computing the gradient vector for scipy optimizers. hess (dict): Method for computing the hessian matrix for scipy optimizers. hessp (callable): Hessian of objective function times an arbitrary vector for scipy optimizers. bounds (sequence or Bounds): Bounds on variables for scipy optimizers. constraints (dict): Constraints definition for scipy optimizers. tol (float): Tolerance of termination for scipy optimizers. callback (callable): Called after each iteration for scipy optimizers. options (dict): a dictionary with options for the different optimizers. compile (bool): whether the TensorFlow graph should be compiled. processes (int): number of processes when using the paralle BFGS method. Return: The final expectation value. The corresponding best parameters. The optimization result object. For scipy methods it returns the ``OptimizeResult``, for ``'cma'`` the ``CMAEvolutionStrategy.result``, and for ``'sgd'`` the options used during the optimization. """ def _loss(params, circuit, hamiltonian): circuit.set_parameters(params) final_state = circuit() return hamiltonian.expectation(final_state) if compile: if K.is_custom: raise_error(RuntimeError, "Cannot compile VQE that uses custom operators. " "Set the compile flag to False.") for gate in self.circuit.queue: _ = gate.cache loss = K.compile(_loss) else: loss = _loss if method == "cma": dtype = getattr(K.np, K._dtypes.get('DTYPE')) loss = lambda p, c, h: dtype(_loss(p, c, h)) elif method != "sgd": loss = lambda p, c, h: K.to_numpy(_loss(p, c, h)) result, parameters, extra = self.optimizers.optimize(loss, initial_state, args=(self.circuit, self.hamiltonian), method=method, jac=jac, hess=hess, hessp=hessp, bounds=bounds, constraints=constraints, tol=tol, callback=callback, options=options, compile=compile, processes=processes) self.circuit.set_parameters(parameters) return result, parameters, extra
def compile(self): """Compiles the circuit as a Tensorflow graph.""" if self._compiled_execute is not None: raise_error(RuntimeError, "Circuit is already compiled.") if not self.queue: raise_error(RuntimeError, "Cannot compile circuit without gates.") for gate in self.queue: # create gate cache before compilation _ = gate.cache self._compiled_execute = K.compile(self._execute_for_compile)
def compile(self): """Compiles the circuit as a Tensorflow graph.""" if self._compiled_execute is not None: raise_error(RuntimeError, "Circuit is already compiled.") if not self.queue: raise_error(RuntimeError, "Cannot compile circuit without gates.") if K.custom_gates: raise_error( RuntimeError, "Cannot compile circuit that uses custom " "operators.") self._compiled_execute = K.compile(self._execute_for_compile)
def test_initial_state_gradient(dtype, compile): # pragma: no cover # Test skipped due to `tf.tensor_scatter_nd_update` bug on GPU def grad_default(var): update = np.array([1]).astype(dtype) with K.optimization.GradientTape() as tape: loss = K.backend.tensor_scatter_nd_update(var, [[0]], update) return tape.gradient(loss, var) def grad_custom(var): with K.optimization.GradientTape() as tape: loss = K.op.initial_state(var) return tape.gradient(loss, var) if compile: grad_default = K.compile(grad_default) grad_custom = K.compile(grad_custom) zeros = K.optimization.Variable(K.zeros(10, dtype=dtype)) grad_reference = grad_default(zeros) grad_custom_op = grad_custom(zeros) np.testing.assert_allclose(grad_reference, grad_custom_op)
def test_apply_swap_with_matrix(compile): """Check ``apply_swap`` for two qubits.""" state = random_complex((2**2, )) matrix = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) target_state = matrix.dot(state.numpy()) def apply_operator(state): qubits = qubits_tensor(2, [0, 1]) return K.op.apply_swap(state, qubits, 2, 0, 1, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy())
def test_initial_state(dtype, compile): """Check that initial_state updates first element properly.""" def apply_operator(dtype): """Apply the initial_state operator""" return K.op.initial_state(nqubits=4, dtype=dtype, is_matrix=False, omp_num_threads=get_threads()) func = apply_operator if compile: func = K.compile(apply_operator) final_state = func(dtype) exact_state = np.array([1] + [0] * 15, dtype=dtype) np.testing.assert_allclose(final_state, exact_state)
def test_apply_gate(nqubits, target, dtype, compile, einsum_str): """Check that ``K.op.apply_gate`` agrees with einsum gate implementation.""" def apply_operator(state, gate): qubits = qubits_tensor(nqubits, [target]) return K.op.apply_gate(state, gate, qubits, nqubits, target, get_threads()) state = random_complex((2**nqubits, ), dtype=dtype) gate = random_complex((2, 2), dtype=dtype) target_state = K.reshape(state, nqubits * (2, )) target_state = K.einsum(einsum_str, target_state, gate) target_state = target_state.numpy().ravel() if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state, gate) np.testing.assert_allclose(target_state, state, atol=_atol)
def test_apply_pauli_gate(nqubits, target, gate, compile): """Check ``apply_x``, ``apply_y`` and ``apply_z`` kernels.""" matrices = { "x": np.array([[0, 1], [1, 0]], dtype=np.complex128), "y": np.array([[0, -1j], [1j, 0]], dtype=np.complex128), "z": np.array([[1, 0], [0, -1]], dtype=np.complex128) } state = random_complex((2**nqubits, )) target_state = K.cast(state, dtype=state.dtype) qubits = qubits_tensor(nqubits, [target]) target_state = K.op.apply_gate(state, matrices[gate], qubits, nqubits, target, get_threads()) def apply_operator(state): qubits = qubits_tensor(nqubits, [target]) return getattr(K.op, "apply_{}".format(gate))(state, qubits, nqubits, target, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state.numpy(), state.numpy())
def test_apply_gate_cx(nqubits, compile): """Check ``K.op.apply_gate`` for multiply-controlled X gates.""" state = random_complex((2**nqubits, )) target_state = np.array(state) gate = np.eye(2**nqubits, dtype=target_state.dtype) gate[-2, -2], gate[-2, -1] = 0, 1 gate[-1, -2], gate[-1, -1] = 1, 0 target_state = np.dot(gate, target_state) xgate = K.cast([[0, 1], [1, 0]], dtype=state.dtype) controls = list(range(nqubits - 1)) def apply_operator(state): qubits = qubits_tensor(nqubits, [nqubits - 1], controls) return K.op.apply_gate(state, xgate, qubits, nqubits, nqubits - 1, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state)
def test_custom_op_toy_callback(gate, compile): """Check calculating ``callbacks`` using intermediate state values.""" import functools state = random_complex((2**2, )) mask = random_complex((2**2, )) matrices = { "h": np.array([[1, 1], [1, -1]]) / np.sqrt(2), "x": np.array([[0, 1], [1, 0]]), "z": np.array([[1, 0], [0, -1]]) } for k, v in matrices.items(): matrices[k] = np.kron(v, np.eye(2)) matrices["swap"] = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) target_state = state.numpy() target_c1 = mask.numpy().dot(target_state) target_state = matrices[gate].dot(target_state) target_c2 = mask.numpy().dot(target_state) assert target_c1 != target_c2 target_callback = [target_c1, target_c2] htf = K.cast(np.array([[1, 1], [1, -1]]) / np.sqrt(2), dtype=state.dtype) qubits_t1 = qubits_tensor(2, [0]) qubits_t2 = qubits_tensor(2, [0, 1]) apply_gate = { "h": functools.partial(K.op.apply_gate, gate=htf, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "x": functools.partial(K.op.apply_x, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "z": functools.partial(K.op.apply_z, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "swap": functools.partial(K.op.apply_swap, qubits=qubits_t2, nqubits=2, target1=0, target2=1, omp_num_threads=get_threads()) } def apply_operator(state): c1 = K.sum(mask * state) state0 = apply_gate[gate](state) c2 = K.sum(mask * state0) return state0, K.stack([c1, c2]) if compile: # pragma: no cover # case not tested because it fails apply_operator = K.compile(apply_operator) state, callback = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy()) np.testing.assert_allclose(target_callback, callback.numpy())