diff --git a/README.md b/README.md index 7f5385f2f1..a35293ef1a 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ Here another example with more gates and shots simulation: import numpy as np from qibo import Circuit, gates -c = Circuit(2) -c.add(gates.X(0)) +circuit = Circuit(2) +circuit.add(gates.X(0)) # Add a measurement register on both qubits -c.add(gates.M(0, 1)) +circuit.add(gates.M(0, 1)) # Execute the circuit with the default initial state |00>. -result = c(nshots=100) +result = circuit(nshots=100) ``` In both cases, the simulation will run in a single device CPU or GPU in double precision `complex128`. diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 9ee57772ee..9fd00e71df 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -40,21 +40,20 @@ Circuit addition .. testsetup:: - import qibo - from qibo import models - from qibo import gates + from qibo import Circuit, gates + from qibo.models import QFT .. testcode:: - c1 = models.QFT(4) + circuit_1 = QFT(4) - c2 = models.Circuit(4) - c2.add(gates.RZ(0, 0.1234)) - c2.add(gates.RZ(1, 0.1234)) - c2.add(gates.RZ(2, 0.1234)) - c2.add(gates.RZ(3, 0.1234)) + circuit_2 = Circuit(4) + circuit_2.add(gates.RZ(0, 0.1234)) + circuit_2.add(gates.RZ(1, 0.1234)) + circuit_2.add(gates.RZ(2, 0.1234)) + circuit_2.add(gates.RZ(3, 0.1234)) - c = c1 + c2 + circuit = circuit_1 + circuit_2 will create a circuit that performs the Quantum Fourier Transform on four qubits followed by Rotation-Z gates. @@ -93,28 +92,28 @@ For example the following: .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates - c = models.Circuit(2) - c.add([gates.H(0), gates.H(1)]) - c.add(gates.CZ(0, 1)) - c.add([gates.X(0), gates.Y(1)]) - fused_c = c.fuse() + circuit = Circuit(2) + circuit.add([gates.H(0), gates.H(1)]) + circuit.add(gates.CZ(0, 1)) + circuit.add([gates.X(0), gates.Y(1)]) + fused_circuit = circuit.fuse() will create a new circuit with a single :class:`qibo.gates.special.FusedGate` acting on ``(0, 1)``, while the following: .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates - c = models.Circuit(3) - c.add([gates.H(0), gates.H(1), gates.H(2)]) - c.add(gates.CZ(0, 1)) - c.add([gates.X(0), gates.Y(1), gates.Z(2)]) - c.add(gates.CNOT(1, 2)) - c.add([gates.H(0), gates.H(1), gates.H(2)]) - fused_c = c.fuse() + circuit = Circuit(3) + circuit.add([gates.H(0), gates.H(1), gates.H(2)]) + circuit.add(gates.CZ(0, 1)) + circuit.add([gates.X(0), gates.Y(1), gates.Z(2)]) + circuit.add(gates.CNOT(1, 2)) + circuit.add([gates.H(0), gates.H(1), gates.H(2)]) + fused_circuit = circuit.fuse() will give a circuit with two fused gates, the first of which will act on ``(0, 1)`` corresponding to @@ -1422,10 +1421,10 @@ The final result of the circuit execution can also be saved to disk and loaded b .. testcode:: - c = Circuit(2) - c.add(gates.M(0,1)) + circuit = Circuit(2) + circuit.add(gates.M(0,1)) # this will be a CircuitResult object - result = c() + result = circuit() # save it to final_result.npy result.dump('final_result.npy') # can be loaded back diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 69466d17a7..6ea78b1836 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -24,7 +24,7 @@ specifies otherwise. One can change the default simulation device using ``qibo.s import qibo qibo.set_device("/CPU:0") - final_state = c() # circuit will now be executed on CPU + final_state = circuit() # circuit will now be executed on CPU The syntax of device names follows the pattern ``'/{device type}:{device number}'`` where device type can be CPU or GPU and the device number is an integer that @@ -96,10 +96,10 @@ be used as follows: # this will use the first GPU three times and the second one time # leading to four total logical devices # construct the distributed circuit for 32 qubits - c = Circuit(32, accelerators) + circuit = Circuit(32, accelerators) -Gates can then be added normally using ``c.add`` and the circuit can be executed -using ``c()``. Note that a ``memory_device`` is passed in the distributed circuit +Gates can then be added normally using ``circuit.add`` and the circuit can be executed +using ``circuit()``. Note that a ``memory_device`` is passed in the distributed circuit (if this is not passed the CPU will be used by default). This device does not perform any gate calculations but is used to store the full state. Therefore the distributed simulation is limited by the amount of CPU memory. @@ -128,11 +128,11 @@ however the user may create the full state as follows: .. code-block:: python # Create distributed circuits for two GPUs - c = Circuit(32, {"/GPU:0": 1, "/GPU:1": 1}) + circuit = Circuit(32, {"/GPU:0": 1, "/GPU:1": 1}) # Add gates - c.add(...) + circuit.add(...) # Execute (``result`` will be a ``DistributedState``) - result = c() + result = circuit() # ``DistributedState`` supports indexing and slicing print(result[40]) @@ -156,21 +156,21 @@ and the :class:`qibo.gates.CallbackGate` gate. For example: .. testcode:: - from qibo import models, gates, callbacks + from qibo import gates, callbacks # create entropy callback where qubit 0 is the first subsystem entropy = callbacks.EntanglementEntropy([0]) # initialize circuit with 2 qubits and add gates - c = models.Circuit(2) # state is |00> (entropy = 0) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation in the initial state - c.add(gates.H(0)) # state is |+0> (entropy = 0) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation after H - c.add(gates.CNOT(0, 1)) # state is |00> + |11> (entropy = 1)) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation after CNOT + circuit = Circuit(2) # state is |00> (entropy = 0) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation in the initial state + circuit.add(gates.H(0)) # state is |+0> (entropy = 0) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation after H + circuit.add(gates.CNOT(0, 1)) # state is |00> + |11> (entropy = 1)) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation after CNOT # execute the circuit using the callback - final_state = c() + final_state = circuit() The results can be accessed using indexing on the callback objects. In this example ``entropy[:]`` will return ``[0, 0, 1]`` which are the @@ -181,29 +181,29 @@ circuit. For example .. testsetup:: - from qibo import models, gates, callbacks + from qibo import Circuit, gates + from qibo.callbacks import EntanglementEntropy # create entropy callback where qubit 0 is the first subsystem - entropy = callbacks.EntanglementEntropy([0]) + entropy = EntanglementEntropy([0]) # initialize circuit with 2 qubits and add gates - c = models.Circuit(2) # state is |00> (entropy = 0) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation in the initial state - c.add(gates.H(0)) # state is |+0> (entropy = 0) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation after H - c.add(gates.CNOT(0, 1)) # state is |00> + |11> (entropy = 1)) - c.add(gates.CallbackGate(entropy)) # performs entropy calculation after CNOT + circuit = Circuit(2) # state is |00> (entropy = 0) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation in the initial state + circuit.add(gates.H(0)) # state is |+0> (entropy = 0) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation after H + circuit.add(gates.CNOT(0, 1)) # state is |00> + |11> (entropy = 1)) + circuit.add(gates.CallbackGate(entropy)) # performs entropy calculation after CNOT # execute the circuit using the callback - final_state = c() + final_state = circuit() .. testcode:: - # c is the same circuit as above # execute the circuit - final_state = c() + final_state = circuit() # execute the circuit a second time - final_state = c() + final_state = circuit() # print result print(entropy[:]) # [0, 0, 1, 0, 0, 1] @@ -228,17 +228,18 @@ such gates are added in a circuit their parameters can be updated using the .. testcode:: from qibo import Circuit, gates + # create a circuit with all parameters set to 0. - c = Circuit(3) - c.add(gates.RX(0, theta=0)) - c.add(gates.RY(1, theta=0)) - c.add(gates.CZ(1, 2)) - c.add(gates.fSim(0, 2, theta=0, phi=0)) - c.add(gates.H(2)) + circuit = Circuit(3) + circuit.add(gates.RX(0, theta=0)) + circuit.add(gates.RY(1, theta=0)) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.fSim(0, 2, theta=0, phi=0)) + circuit.add(gates.H(2)) # set new values to the circuit's parameters params = [0.123, 0.456, (0.789, 0.321)] - c.set_parameters(params) + circuit.set_parameters(params) initializes a circuit with all gate parameters set to 0 and then updates the values of these parameters according to the ``params`` list. Alternatively the @@ -252,21 +253,21 @@ the circuit. For example: .. testcode:: - c = Circuit(3) + circuit = Circuit(3) g0 = gates.RX(0, theta=0) g1 = gates.RY(1, theta=0) g2 = gates.fSim(0, 2, theta=0, phi=0) - c.add([g0, g1, gates.CZ(1, 2), g2, gates.H(2)]) + circuit.add([g0, g1, gates.CZ(1, 2), g2, gates.H(2)]) # set new values to the circuit's parameters using a dictionary params = {g0: 0.123, g1: 0.456, g2: (0.789, 0.321)} - c.set_parameters(params) + circuit.set_parameters(params) # equivalently the parameter's can be update with a list as params = [0.123, 0.456, (0.789, 0.321)] - c.set_parameters(params) + circuit.set_parameters(params) # or with a flat list as params = [0.123, 0.456, 0.789, 0.321] - c.set_parameters(params) + circuit.set_parameters(params) If a list is given then its length and elements should be compatible with the parametrized gates contained in the circuit. If a dictionary is given then its @@ -278,12 +279,14 @@ The following gates support parameter setting: * :class:`qibo.gates.fSim`: Accepts a tuple of two parameters ``(theta, phi)``. * :class:`qibo.gates.GeneralizedfSim`: Accepts a tuple of two parameters ``(unitary, phi)``. Here ``unitary`` should be a unitary matrix given as an - array or ``tf.Tensor`` of shape ``(2, 2)``. A ``torch.Tensor`` is required when using the pytorch backend. + array or ``tf.Tensor`` of shape ``(2, 2)``. A ``torch.Tensor`` is required + when using the pytorch backend. * :class:`qibo.gates.Unitary`: Accepts a single ``unitary`` parameter. This - should be an array or ``tf.Tensor`` of shape ``(2, 2)``. A ``torch.Tensor`` is required when using the pytorch backend. + should be an array or ``tf.Tensor`` of shape ``(2, 2)``. + A ``torch.Tensor`` is required when using the pytorch backend. Note that a ``np.ndarray`` or a ``tf.Tensor`` may also be used in the place of -a flat list (``torch.Tensor`` is required when using the pytorch backend). +a flat list (``torch.Tensor`` is required when using the ``pytorch`` backend). Using :meth:`qibo.models.circuit.Circuit.set_parameters` is more efficient than recreating a new circuit with new parameter values. The inverse method :meth:`qibo.models.circuit.Circuit.get_parameters` is also available @@ -301,12 +304,12 @@ the ``trainable=False`` during gate creation. For example: .. testcode:: - c = Circuit(3) - c.add(gates.RX(0, theta=0.123)) - c.add(gates.RY(1, theta=0.456, trainable=False)) - c.add(gates.fSim(0, 2, theta=0.789, phi=0.567)) + circuit = Circuit(3) + circuit.add(gates.RX(0, theta=0.123)) + circuit.add(gates.RY(1, theta=0.456, trainable=False)) + circuit.add(gates.fSim(0, 2, theta=0.789, phi=0.567)) - print(c.get_parameters()) + print(circuit.get_parameters()) # prints [(0.123,), (0.789, 0.567)] ignoring the parameters of the RY gate .. testoutput:: @@ -338,11 +341,11 @@ of the :class:`qibo.gates.M` gate. For example from qibo import Circuit, gates - c = Circuit(1, density_matrix=True) - c.add(gates.H(0)) - output = c.add(gates.M(0, collapse=True)) - c.add(gates.H(0)) - result = c(nshots=1) + circuit = Circuit(1, density_matrix=True) + circuit.add(gates.H(0)) + output = circuit.add(gates.M(0, collapse=True)) + circuit.add(gates.H(0)) + result = circuit(nshots=1) print(result) # prints |+><+| if 0 is measured # or |-><-| if 1 is measured @@ -369,16 +372,16 @@ a loop: from qibo import Circuit, gates - c = Circuit(1, density_matrix=True) - c.add(gates.H(0)) - output = c.add(gates.M(0, collapse=True)) - c.add(gates.H(0)) + circuit = Circuit(1, density_matrix=True) + circuit.add(gates.H(0)) + output = circuit.add(gates.M(0, collapse=True)) + circuit.add(gates.H(0)) nshots = 100 .. testcode:: for _ in range(nshots): - result = c() + result = circuit() Note that this will be more time-consuming compared to multi-shot simulation of standard (non-collapse) measurements where the circuit is simulated once and @@ -393,13 +396,13 @@ also possible: from qibo import Circuit, gates - c = Circuit(2) - c.add(gates.H(0)) - c.add(gates.H(1)) - output = c.add(gates.M(0, collapse=True)) - c.add(gates.H(0)) - c.add(gates.M(0, 1)) - result = c(nshots=100) + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + output = circuit.add(gates.M(0, collapse=True)) + circuit.add(gates.H(0)) + circuit.add(gates.M(0, 1)) + result = circuit(nshots=100) In this case ``output`` will contain the results of the first ``collapse=True`` measurement while ``result`` will contain the results of the standard measurement. @@ -415,11 +418,11 @@ any parametrized gate as follows: import numpy as np from qibo import Circuit, gates - c = Circuit(2, density_matrix=True) - c.add(gates.H(0)) - output = c.add(gates.M(0, collapse=True)) - c.add(gates.RX(1, theta=np.pi * output.symbols[0] / 4)) - result = c() + circuit = Circuit(2, density_matrix=True) + circuit.add(gates.H(0)) + output = circuit.add(gates.M(0, collapse=True)) + circuit.add(gates.RX(1, theta=np.pi * output.symbols[0] / 4)) + result = circuit() In this case the first qubit will be measured and if 1 is found a pi/4 X-rotation will be applied to the second qubit, otherwise no rotation. Qibo allows to @@ -435,14 +438,15 @@ If more than one qubits are used in a ``collapse=True`` measurement gate the .. testcode:: import numpy as np + from qibo import Circuit, gates - c = Circuit(3, density_matrix=True) - c.add(gates.H(0)) - output = c.add(gates.M(0, 1, collapse=True)) - c.add(gates.RX(1, theta=np.pi * output.symbols[0] / 4)) - c.add(gates.RY(2, theta=np.pi * (output.symbols[0] + output.symbols[1]) / 5)) - result = c() + circuit = Circuit(3, density_matrix=True) + circuit.add(gates.H(0)) + output = circuit.add(gates.M(0, 1, collapse=True)) + circuit.add(gates.RX(1, theta=np.pi * output.symbols[0] / 4)) + circuit.add(gates.RY(2, theta=np.pi * (output.symbols[0] + output.symbols[1]) / 5)) + result = circuit() How to invert a circuit? @@ -479,21 +483,24 @@ method. For example: .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates + from qibo.models import QFT # Create a small circuit of 4 qubits - smallc = models.Circuit(4) - smallc.add((gates.RX(i, theta=0.1) for i in range(4))) - smallc.add((gates.CNOT(0, 1), gates.CNOT(2, 3))) + nqubits = 4 + small_circuit = Circuit(nqubits) + small_circuit.add((gates.RX(i, theta=0.1) for i in range(4))) + small_circuit.add((gates.CNOT(0, 1), gates.CNOT(2, 3))) # Create a large circuit on 8 qubits - largec = models.Circuit(8) + nqubits = 8 + large_circuit = Circuit(nqubits) # Add the small circuit on even qubits - largec.add(smallc.on_qubits(*range(0, 8, 2))) + large_circuit.add(small_circuit.on_qubits(*range(0, nqubits, 2))) # Add a QFT on odd qubits - largec.add(models.QFT(4).on_qubits(*range(1, 8, 2))) + large_circuit.add(QFT(4).on_qubits(*range(1, nqubits, 2))) # Add an inverse QFT on first 6 qubits - largec.add(models.QFT(6).invert().on_qubits(*range(6))) + large_circuit.add(QFT(6).invert().on_qubits(*range(6))) .. _vqe-example: @@ -511,29 +518,33 @@ Here is a simple example using the Heisenberg XXZ model Hamiltonian: .. testcode:: import numpy as np - from qibo import models, gates, hamiltonians + + from qibo import Circuit, gates, hamiltonians + from qibo.hamiltonians import XXZ + from qibo.models import VQE nqubits = 6 nlayers = 4 # Create variational circuit - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(nlayers): - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) - circuit.add((gates.CZ(q, q+1) for q in range(0, nqubits-1, 2))) - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) - circuit.add((gates.CZ(q, q+1) for q in range(1, nqubits-2, 2))) - circuit.add(gates.CZ(0, nqubits-1)) - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(0, nqubits - 1, 2)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(1, nqubits - 2, 2)) + circuit.add(gates.CZ(0, nqubits - 1)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) # Create XXZ Hamiltonian - hamiltonian = hamiltonians.XXZ(nqubits=nqubits) + hamiltonian = XXZ(nqubits=nqubits) + # Create VQE model - vqe = models.VQE(circuit, hamiltonian) + vqe = VQE(circuit, hamiltonian) # Optimize starting from a random guess for the variational parameters - initial_parameters = np.random.uniform(0, 2*np.pi, - 2*nqubits*nlayers + nqubits) + initial_parameters = np.random.uniform(0, 2 * np.pi, nqubits * (2 * nlayers + 1)) + best, params, extra = vqe.minimize(initial_parameters, method='BFGS', compile=False) @@ -555,19 +566,18 @@ general two-qubit gates (as 4x4 matrices). .. testsetup:: - import numpy as np - from qibo import models, gates, hamiltonians + from qibo import Circuit, gates .. testcode:: - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(nlayers): - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) - circuit.add((gates.CZ(q, q+1) for q in range(0, nqubits-1, 2))) - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) - circuit.add((gates.CZ(q, q+1) for q in range(1, nqubits-2, 2))) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(0, nqubits - 1, 2)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(1, nqubits - 2, 2)) circuit.add(gates.CZ(0, nqubits-1)) - circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) + circuit.add(gates.RY(qubit, theta=0) for qubit in range(nqubits)) circuit = circuit.fuse() .. _vqc-example: @@ -584,37 +594,40 @@ Here is a simple example using a custom loss function: .. testcode:: import numpy as np - from qibo import models, gates + + from qibo import Circuit, gates from qibo.optimizers import optimize + from qibo.quantum_info import infidelity # custom loss function, computes fidelity def myloss(parameters, circuit, target): circuit.set_parameters(parameters) final_state = circuit().state() - return 1 - np.abs(np.conj(target).dot(final_state)) + return infidelity(final_state, target) nqubits = 6 + dims = 2**nqubits nlayers = 2 # Create variational circuit - c = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(nlayers): - c.add((gates.RY(q, theta=0) for q in range(nqubits))) - c.add((gates.CZ(q, q+1) for q in range(0, nqubits-1, 2))) - c.add((gates.RY(q, theta=0) for q in range(nqubits))) - c.add((gates.CZ(q, q+1) for q in range(1, nqubits-2, 2))) - c.add(gates.CZ(0, nqubits-1)) - c.add((gates.RY(q, theta=0) for q in range(nqubits))) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(0, nqubits - 1, 2)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(1, nqubits - 2, 2)) + circuit.add(gates.CZ(0, nqubits - 1)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) # Optimize starting from a random guess for the variational parameters - x0 = np.random.uniform(0, 2*np.pi, 2*nqubits*nlayers + nqubits) - data = np.random.normal(0, 1, size=2**nqubits) + x0 = np.random.uniform(0, 2 * np.pi, nqubits * (2 * nlayers + 1)) + data = np.random.normal(0, 1, size=dims) # perform optimization - best, params, extra = optimize(myloss, x0, args=(c, data), method='BFGS') + best, params, extra = optimize(myloss, x0, args=(circuit, data), method='BFGS') # set final solution to circuit instance - c.set_parameters(params) + circuit.set_parameters(params) .. _qaoa-example: @@ -701,9 +714,10 @@ installed and used as provider of these quantum machine learning backends. .. code-block:: python - import qibo - qibo.set_backend(backend="qiboml", platform="tensorflow") - from qibo import gates, models + from qibo import Circuit, gates, set_backend + from qibo.quantum_info import infidelity + + set_backend(backend="qiboml", platform="tensorflow") backend = qibo.get_backend() tf = backend.tf @@ -717,16 +731,16 @@ installed and used as provider of these quantum machine learning backends. params = tf.Variable( tf.random.uniform((2,), dtype=tf.float64) ) - c = models.Circuit(2) - c.add(gates.RX(0, params[0])) - c.add(gates.RY(1, params[1])) + + circuit = Circuit(2) + circuit.add(gates.RX(0, params[0])) + circuit.add(gates.RY(1, params[1])) for _ in range(nepochs): with tf.GradientTape() as tape: - c.set_parameters(params) - final_state = c().state() - fidelity = tf.math.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) - loss = 1 - fidelity + circuit.set_parameters(params) + final_state = circuit().state() + loss = infidelity(final_state, target_state, backend=backend) grads = tape.gradient(loss, params) optimizer.apply_gradients(zip([grads], [params])) @@ -736,15 +750,16 @@ automatic differentiation tools. To be constructed, the Qiboml package has to be installed and used. The optimization procedure may also be compiled, however in this case it is not -possible to use :meth:`qibo.circuit.Circuit.set_parameters` as the +possible to use :meth:`qibo.models.Circuit.set_parameters` as the circuit needs to be defined inside the compiled ``tf.GradientTape()``. For example: .. code-block:: python - import qibo - qibo.set_backend(backend="qiboml", platform="tensorflow") - from qibo import gates, models + from qibo import Circuit, gates, set_backend + from qibo.quantum_info import infidelity + + set_backend(backend="qiboml", platform="tensorflow") backend = qibo.get_backend() tf = backend.tf @@ -757,12 +772,11 @@ For example: @tf.function def optimize(params): with tf.GradientTape() as tape: - c = models.Circuit(2) - c.add(gates.RX(0, theta=params[0])) - c.add(gates.RY(1, theta=params[1])) - final_state = c().state() - fidelity = tf.math.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) - loss = 1 - fidelity + circuit = Circuit(2) + circuit.add(gates.RX(0, theta=params[0])) + circuit.add(gates.RY(1, theta=params[1])) + final_state = circuit().state() + loss = infidelity(final_state, target_state, backend=backend) grads = tape.gradient(loss, params) optimizer.apply_gradients(zip([grads], [params])) @@ -773,18 +787,20 @@ For example: The user may also use ``tf.Variable`` and parametrized gates in any other way that is supported by Tensorflow, such as defining `custom Keras layers `_ -and using the `Sequential model API `_ -to train them. +and using the `Sequential model API +`_ to train them. -Similarly, Pytorch supports `automatic differentiation `_. -The following script optimizes the parameters of the variational circuit of the first example using the Pytorch framework. +Similarly, ``pytorch`` `supports automatic differentiation +`_. +The following script optimizes the parameters of the variational circuit of the first example +using the ``pytorch`` framework. .. code-block:: python - import qibo - qibo.set_backend("pytorch") import torch - from qibo import gates, models + + from qibo import Circuit, gates, set_backend + set_backend("pytorch") # Optimization parameters nepochs = 1000 @@ -795,18 +811,17 @@ The following script optimizes the parameters of the variational circuit of the params = torch.tensor( torch.rand(2, dtype=torch.float64), requires_grad=True ) - c = models.Circuit(2) - c.add(gates.RX(0, params[0])) - c.add(gates.RY(1, params[1])) + circuit = Circuit(2) + circuit.add(gates.RX(0, params[0])) + circuit.add(gates.RY(1, params[1])) optimizer = optimizer([params]) for _ in range(nepochs): optimizer.zero_grad() - c.set_parameters(params) - final_state = c().state() - fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) - loss = 1 - fidelity + circuit.set_parameters(params) + final_state = circuit().state() + loss = infidelity(final_state, target_state) loss.backward() optimizer.step() @@ -840,19 +855,20 @@ Qibo circuits can evolve density matrices if they are initialized using the import qibo qibo.set_backend("qibojit") - from qibo import models, gates + from qibo import Circuit, gates # Define circuit - c = models.Circuit(2, density_matrix=True) - c.add(gates.H(0)) - c.add(gates.H(1)) + circuit = Circuit(2, density_matrix=True) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) # execute using the default initial state |00><00| - result = c() # will be |++><++| + result = circuit() # will be |++><++| will perform the transformation .. math:: - |00 \rangle \langle 00| \rightarrow (H_1 \otimes H_2)|00 \rangle \langle 00|(H_1 \otimes H_2)^\dagger = |++ \rangle \langle ++| + |00 \rangle \langle 00| \rightarrow (H_1 \otimes H_2)|00 \rangle \langle 00| + (H_1 \otimes H_2)^\dagger = |++ \rangle \langle ++| Similarly to state vector circuit simulation, the user may specify a custom initial density matrix by passing the corresponding array when executing the @@ -867,23 +883,24 @@ for example: .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates - c = models.Circuit(2, density_matrix=True) # starts with state |00><00| - c.add(gates.X(1)) + circuit = Circuit(2, density_matrix=True) # starts with state |00><00| + circuit.add(gates.X(1)) # transforms |00><00| -> |01><01| - c.add(gates.PauliNoiseChannel(0, [("X", 0.3)])) + circuit.add(gates.PauliNoiseChannel(0, [("X", 0.3)])) # transforms |01><01| -> (1 - px)|01><01| + px |11><11| - result = c() + result = circuit() # result.state() will be tf.Tensor(diag([0, 0.7, 0, 0.3])) will perform the transformation .. math:: |00\rangle \langle 00|& \rightarrow (I \otimes X)|00\rangle \langle 00|(I \otimes X) - = |01\rangle \langle 01| - \\& \rightarrow 0.7|01\rangle \langle 01| + 0.3(X\otimes I)|01\rangle \langle 01|(X\otimes I)^\dagger - \\& = 0.7|01\rangle \langle 01| + 0.3|11\rangle \langle 11| + = |01\rangle \langle 01| + \\& \rightarrow 0.7|01\rangle \langle 01| + 0.3(X\otimes I) + |01\rangle \langle 01|(X\otimes I)^\dagger + \\& = 0.7|01\rangle \langle 01| + 0.3|11\rangle \langle 11| Measurements and callbacks can be used with density matrices exactly as in the case of state vector simulation. @@ -904,20 +921,25 @@ as follows: .. testcode:: import numpy as np - from qibo import models, gates + from qibo import Circuit, gates + + nqubits = 5 + nshots = 1000 # Define circuit - c = models.Circuit(5) - thetas = np.random.random(5) - c.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) + circuit = Circuit(nqubits) + thetas = np.random.random(nqubits) + circuit.add(gates.RX(qubit, theta=phase) for qubit, phase in enumerate(thetas)) # Add noise channels to all qubits - c.add((gates.PauliNoiseChannel(i, [("X", 0.2), ("Y", 0.0), ("Z", 0.3)]) - for i in range(5))) + circuit.add( + gates.PauliNoiseChannel(qubit, [("X", 0.2), ("Y", 0.0), ("Z", 0.3)]) + for qubit in range(nqubits) + ) # Add measurement of all qubits - c.add(gates.M(*range(5))) + circuit.add(gates.M(*range(5))) # Repeat execution 1000 times - result = c(nshots=1000) + result = circuit(nshots=nshots) In this example the simulation is repeated 1000 times and the action of the :class:`qibo.gates.PauliNoiseChannel` gate differs each time, because @@ -962,30 +984,30 @@ triplets. For example, the following script .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates - c = models.Circuit(2) - c.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)]) + circuit = Circuit(2) + circuit.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)]) - # Define a noise map that maps qubit IDs to noise probabilities - noise_map = {0: list(zip(["X", "Z"], [0.1, 0.2])), 1: list(zip(["Y", "Z"], [0.2, 0.1]))} - noisy_c = c.with_pauli_noise(noise_map) + # Define a noise map that maps qubit IDs to noise probabilities + noise_map = {0: list(zip(["X", "Z"], [0.1, 0.2])), 1: list(zip(["Y", "Z"], [0.2, 0.1]))} + noisy_circuit = circuit.with_pauli_noise(noise_map) -will create a new circuit ``noisy_c`` that is equivalent to: +will create a new circuit ``noisy_circuit`` that is equivalent to: .. testcode:: - noisy_c2 = models.Circuit(2) - noisy_c2.add(gates.H(0)) - noisy_c2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Y", 0.0), ("Z", 0.2)])) - noisy_c2.add(gates.H(1)) - noisy_c2.add(gates.PauliNoiseChannel(1, [("X", 0.0), ("Y", 0.2), ("Z", 0.1)])) - noisy_c2.add(gates.CNOT(0, 1)) - noisy_c2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Y", 0.0), ("Z", 0.2)])) - noisy_c2.add(gates.PauliNoiseChannel(1, [("X", 0.0), ("Y", 0.2), ("Z", 0.1)])) - -Note that ``noisy_c`` uses the gate objects of the original circuit ``c`` -(it is not a deep copy), while in ``noisy_c2`` each gate was created as + noisy_circuit_2 = Circuit(2) + noisy_circuit_2.add(gates.H(0)) + noisy_circuit_2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Y", 0.0), ("Z", 0.2)])) + noisy_circuit_2.add(gates.H(1)) + noisy_circuit_2.add(gates.PauliNoiseChannel(1, [("X", 0.0), ("Y", 0.2), ("Z", 0.1)])) + noisy_circuit_2.add(gates.CNOT(0, 1)) + noisy_circuit_2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Y", 0.0), ("Z", 0.2)])) + noisy_circuit_2.add(gates.PauliNoiseChannel(1, [("X", 0.0), ("Y", 0.2), ("Z", 0.1)])) + +Note that ``noisy_circuit`` uses the gate objects of the original circuit ``circuit`` +(it is not a deep copy), while in ``noisy_circuit_2`` each gate was created as a new object. The user may use a single tuple instead of a dictionary as the noise map @@ -1021,42 +1043,52 @@ Here is an example on how to use a noise model: .. testcode:: - import numpy as np - from qibo import models, gates - from qibo.noise import NoiseModel, PauliError - - # Build specific noise model with 3 quantum errors: - # - Pauli error on H only for qubit 1. - # - Pauli error on CNOT for all the qubits. - # - Pauli error on RX(pi/2) for qubit 0. - noise = NoiseModel() - noise.add(PauliError([("X", 0.5)]), gates.H, 1) - noise.add(PauliError([("Y", 0.5)]), gates.CNOT) - is_sqrt_x = (lambda g: np.pi/2 in g.parameters) - noise.add(PauliError([("X", 0.5)]), gates.RX, qubits=0, conditions=is_sqrt_x) + import numpy as np - # Generate noiseless circuit. - c = models.Circuit(2) - c.add([gates.H(0), gates.H(1), gates.CNOT(0, 1), gates.RX(0, np.pi/2), gates.RX(0, 3*np.pi/2), gates.RX(1, np.pi/2)]) + from qibo import Circuit, gates + from qibo.noise import NoiseModel, PauliError + + # Build specific noise model with 3 quantum errors: + # - Pauli error on H only for qubit 1. + # - Pauli error on CNOT for all the qubits. + # - Pauli error on RX(pi/2) for qubit 0. + noise = NoiseModel() + noise.add(PauliError([("X", 0.5)]), gates.H, 1) + noise.add(PauliError([("Y", 0.5)]), gates.CNOT) + is_sqrt_x = (lambda g: np.pi / 2 in g.parameters) + noise.add(PauliError([("X", 0.5)]), gates.RX, qubits=0, conditions=is_sqrt_x) + + # Generate noiseless circuit. + circuit = Circuit(2) + circuit.add( + [ + gates.H(0), + gates.H(1), + gates.CNOT(0, 1), + gates.RX(0, np.pi / 2), + gates.RX(0, 3 * np.pi / 2), + gates.RX(1, np.pi / 2), + ] + ) - # Apply noise to the circuit according to the noise model. - noisy_c = noise.apply(c) + # Apply noise to the circuit according to the noise model. + noisy_circuit = noise.apply(circuit) The noisy circuit defined above will be equivalent to the following circuit: .. testcode:: - noisy_c2 = models.Circuit(2) - noisy_c2.add(gates.H(0)) - noisy_c2.add(gates.H(1)) - noisy_c2.add(gates.PauliNoiseChannel(1, [("X", 0.5)])) - noisy_c2.add(gates.CNOT(0, 1)) - noisy_c2.add(gates.PauliNoiseChannel(0, [("Y", 0.5)])) - noisy_c2.add(gates.PauliNoiseChannel(1, [("Y", 0.5)])) - noisy_c2.add(gates.RX(0, np.pi/2)) - noisy_c2.add(gates.PauliNoiseChannel(0, [("X", 0.5)])) - noisy_c2.add(gates.RX(0, 3*np.pi/2)) - noisy_c2.add(gates.RX(1, np.pi/2)) + noisy_circuit_2 = Circuit(2) + noisy_circuit_2.add(gates.H(0)) + noisy_circuit_2.add(gates.H(1)) + noisy_circuit_2.add(gates.PauliNoiseChannel(1, [("X", 0.5)])) + noisy_circuit_2.add(gates.CNOT(0, 1)) + noisy_circuit_2.add(gates.PauliNoiseChannel(0, [("Y", 0.5)])) + noisy_circuit_2.add(gates.PauliNoiseChannel(1, [("Y", 0.5)])) + noisy_circuit_2.add(gates.RX(0, np.pi / 2)) + noisy_circuit_2.add(gates.PauliNoiseChannel(0, [("X", 0.5)])) + noisy_circuit_2.add(gates.RX(0, 3 * np.pi / 2)) + noisy_circuit_2.add(gates.RX(1, np.pi / 2)) The :class:`qibo.noise.NoiseModel` class supports also density matrices, @@ -1074,19 +1106,20 @@ re-execute the simulation. For example: .. testcode:: - import numpy as np - from qibo import models, gates + import numpy as np - thetas = np.random.random(4) - c = models.Circuit(4) - c.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) - c.add([gates.M(0, 1), gates.M(2, 3)]) - result = c(nshots=100) - # add bit-flip errors with probability 0.2 for all qubits - result.apply_bitflips(0.2) - # add bit-flip errors with different probabilities for each qubit - error_map = {0: 0.2, 1: 0.1, 2: 0.3, 3: 0.1} - result.apply_bitflips(error_map) + from qibo import Circuit, gates + + thetas = np.random.random(4) + circuit = Circuit(4) + circuit.add(gates.RX(i, theta=t) for i, t in enumerate(thetas)) + circuit.add((gates.M(0, 1), gates.M(2, 3))) + result = circuit(nshots=100) + # add bit-flip errors with probability 0.2 for all qubits + result.apply_bitflips(0.2) + # add bit-flip errors with different probabilities for each qubit + error_map = {0: 0.2, 1: 0.1, 2: 0.3, 3: 0.1} + result.apply_bitflips(error_map) The corresponding noisy samples and frequencies can then be obtained as described in the :ref:`How to perform measurements? ` example. @@ -1099,19 +1132,22 @@ the bitflips: .. testcode:: - import numpy as np - from qibo import models, gates + import numpy as np + + from qibo import Circuit, gates + + nqubits = 4 - thetas = np.random.random(4) - c = models.Circuit(4) - c.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) - c.add([gates.M(0, 1), gates.M(2, 3)]) - result = c(nshots=100) - # add bit-flip errors with probability 0.2 for all qubits - result.apply_bitflips(0.2) - # add bit-flip errors with different probabilities for each qubit - error_map = {0: 0.2, 1: 0.1, 2: 0.3, 3: 0.1} - result.apply_bitflips(error_map) + thetas = np.random.random(nqubits) + circuit = Circuit(nqubits) + circuit.add(gates.RX(qubit, theta=phase) for qubit, phase in enumerate(thetas)) + circuit.add([gates.M(0, 1), gates.M(2, 3)]) + result = circuit(nshots=100) + # add bit-flip errors with probability 0.2 for all qubits + result.apply_bitflips(0.2) + # add bit-flip errors with different probabilities for each qubit + error_map = {0: 0.2, 1: 0.1, 2: 0.3, 3: 0.1} + result.apply_bitflips(error_map) Alternatively, the user may specify a bit-flip error map when defining @@ -1119,16 +1155,17 @@ measurement gates: .. testcode:: - import numpy as np - from qibo import models, gates + import numpy as np - thetas = np.random.random(6) - c = models.Circuit(6) - c.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) - c.add(gates.M(0, 1, p0=0.2)) - c.add(gates.M(2, 3, p0={2: 0.1, 3: 0.0})) - c.add(gates.M(4, 5, p0=[0.4, 0.3])) - result = c(nshots=100) + from qibo import Circuit, gates + + thetas = np.random.random(6) + circuit = Circuit(6) + circuit.add(gates.RX(qubit, theta=phase) for qubit, phase in enumerate(thetas)) + circuit.add(gates.M(0, 1, p0=0.2)) + circuit.add(gates.M(2, 3, p0={2: 0.1, 3: 0.0})) + circuit.add(gates.M(4, 5, p0=[0.4, 0.3])) + result = circuit(nshots=100) In this case ``result`` will contain noisy samples according to the given bit-flip probabilities. The probabilities can be given as a @@ -1259,23 +1296,23 @@ Let's see how to use them. For starters, let's define a dummy circuit with some hz = 0.5 hx = 0.5 dt = 0.25 - circ = Circuit(nqubits, density_matrix=True) - circ.add(gates.RZ(q, theta=-2 * hz * dt - np.pi / 2) for q in range(nqubits)) - circ.add(gates.RX(q, theta=np.pi / 2) for q in range(nqubits)) - circ.add(gates.RZ(q, theta=-2 * hx * dt + np.pi) for q in range(nqubits)) - circ.add(gates.RX(q, theta=np.pi / 2) for q in range(nqubits)) - circ.add(gates.RZ(q, theta=-np.pi / 2) for q in range(nqubits)) - circ.add(gates.CNOT(q, q + 1) for q in range(0, nqubits - 1, 2)) - circ.add(gates.RZ(q + 1, theta=-2 * dt) for q in range(0, nqubits - 1, 2)) - circ.add(gates.CNOT(q, q + 1) for q in range(0, nqubits - 1, 2)) - circ.add(gates.CNOT(q, q + 1) for q in range(1, nqubits, 2)) - circ.add(gates.RZ(q + 1, theta=-2 * dt) for q in range(1, nqubits, 2)) - circ.add(gates.CNOT(q, q + 1) for q in range(1, nqubits, 2)) + circuit = Circuit(nqubits, density_matrix=True) + circuit.add(gates.RZ(q, theta=-2 * hz * dt - np.pi / 2) for q in range(nqubits)) + circuit.add(gates.RX(q, theta=np.pi / 2) for q in range(nqubits)) + circuit.add(gates.RZ(q, theta=-2 * hx * dt + np.pi) for q in range(nqubits)) + circuit.add(gates.RX(q, theta=np.pi / 2) for q in range(nqubits)) + circuit.add(gates.RZ(q, theta=-np.pi / 2) for q in range(nqubits)) + circuit.add(gates.CNOT(q, q + 1) for q in range(0, nqubits - 1, 2)) + circuit.add(gates.RZ(q + 1, theta=-2 * dt) for q in range(0, nqubits - 1, 2)) + circuit.add(gates.CNOT(q, q + 1) for q in range(0, nqubits - 1, 2)) + circuit.add(gates.CNOT(q, q + 1) for q in range(1, nqubits, 2)) + circuit.add(gates.RZ(q + 1, theta=-2 * dt) for q in range(1, nqubits, 2)) + circuit.add(gates.CNOT(q, q + 1) for q in range(1, nqubits, 2)) # Include the measurements - circ.add(gates.M(*range(nqubits))) + circuit.add(gates.M(*range(nqubits))) # visualize the circuit - circ.draw() + circuit.draw() # q0: ─RZ─RX─RZ─RX─RZ─o────o────────M─ # q1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─ @@ -1307,7 +1344,7 @@ the real quantum hardware, instead, we can use a noise model: .. testcode:: # Noise-free expected value - exact = obs.expectation(backend.execute_circuit(circ).state()) + exact = obs.expectation(backend.execute_circuit(circuit).state()) print(exact) # 0.9096065335014379 @@ -1325,7 +1362,7 @@ the real quantum hardware, instead, we can use a noise model: ) noise.add(ReadoutError(probabilities=prob), gate=gates.M) # Noisy expected value without mitigation - noisy = obs.expectation(backend.execute_circuit(noise.apply(circ)).state()) + noisy = obs.expectation(backend.execute_circuit(noise.apply(circuit)).state()) print(noisy) # 0.5647937721701448 @@ -1356,7 +1393,7 @@ response matrix and use it modify the final state after the circuit execution: # define mitigation options readout = {"response_matrix": response_matrix} # mitigate the readout errors - mit_val = get_expectation_val_with_readout_mitigation(circ, obs, noise, readout=readout) + mit_val = get_expectation_val_with_readout_mitigation(circuit, obs, noise, readout=readout) print(mit_val) # 0.5945794816381054 @@ -1374,7 +1411,7 @@ Or use the randomized readout mitigation: # define mitigation options readout = {"ncircuits": 10} # mitigate the readout errors - mit_val = get_expectation_val_with_readout_mitigation(circ, obs, noise, readout=readout) + mit_val = get_expectation_val_with_readout_mitigation(circuit, obs, noise, readout=readout) print(mit_val) # 0.5860884499785314 @@ -1414,7 +1451,7 @@ For example if we use the five levels ``[0,1,2,3,4]`` : # Mitigated expected value estimate = ZNE( - circuit=circ, + circuit=circuit, observable=obs, noise_levels=np.arange(5), noise_model=noise, @@ -1443,7 +1480,7 @@ combined with the readout mitigation: # Mitigated expected value estimate = ZNE( - circuit=circ, + circuit=circuit, observable=obs, backend=backend, noise_levels=np.arange(5), @@ -1473,7 +1510,7 @@ circuit is expected to be decomposed in the set of primitive gates :math:`RX(\fr # Mitigated expected value estimate = CDR( - circuit=circ, + circuit=circuit, observable=obs, n_training_samples=10, backend=backend, @@ -1504,7 +1541,7 @@ caveat about the input circuit for CDR is valid here as well. # Mitigated expected value estimate = vnCDR( - circuit=circ, + circuit=circuit, observable=obs, n_training_samples=10, backend=backend, @@ -1538,7 +1575,7 @@ The use of iCS is straightforward, analogous to CDR and vnCDR. # Mitigated expected value estimate = ICS( - circuit=circ, + circuit=circuit, observable=obs, n_training_samples=10, backend=backend, @@ -1737,17 +1774,19 @@ Here is an example of adiabatic evolution simulation: .. testcode:: import numpy as np - from qibo import hamiltonians, models + + from qibo.hamiltonians import TFIM, X + from qibo.models import AdiabaticEvolution nqubits = 4 T = 1 # total evolution time # Define the easy and hard Hamiltonians - h0 = hamiltonians.X(nqubits) - h1 = hamiltonians.TFIM(nqubits, h=0) + h0 = X(nqubits) + h1 = TFIM(nqubits, h=0) # Define the interpolation scheduling s = lambda t: t # Define evolution model - evolve = models.AdiabaticEvolution(h0, h1, s, dt=1e-2) + evolve = AdiabaticEvolution(h0, h1, s, dt=1e-2) # Get the final state of the evolution final_state = evolve(final_time=T) @@ -1770,18 +1809,26 @@ similar to other callbacks: .. testcode:: import numpy as np - from qibo import hamiltonians, models, callbacks + + from qibo.callbacks import Gap + from qibo.hamiltonians import TFIM, X + from qibo.models import AdiabaticEvolution nqubits = 4 - h0 = hamiltonians.X(nqubits) - h1 = hamiltonians.TFIM(nqubits, h=0) + h0 = X(nqubits) + h1 = TFIM(nqubits, h=0) - ground = callbacks.Gap(mode=0) + ground = Gap(mode=0) # define a callback for calculating the gap - gap = callbacks.Gap() + gap = Gap() # define and execute the ``AdiabaticEvolution`` model - evolution = models.AdiabaticEvolution(h0, h1, lambda t: t, dt=1e-1, - callbacks=[gap, ground]) + evolution = AdiabaticEvolution( + h0, + h1, + lambda t: t, + dt=1e-1, + callbacks=[gap, ground] + ) final_state = evolution(final_time=1.0) # print the values of the gap at each evolution time step @@ -1809,14 +1856,15 @@ pre-coded Hamiltonians this can be done simply as: .. testcode:: - from qibo import hamiltonians, models + from qibo.hamiltonians import TFIM, X + from qibo.models import AdiabaticEvolution nqubits = 4 # Define ``SymolicHamiltonian``s - h0 = hamiltonians.X(nqubits, dense=False) - h1 = hamiltonians.TFIM(nqubits, h=0, dense=False) + h0 = X(nqubits, dense=False) + h1 = TFIM(nqubits, h=0, dense=False) # Perform adiabatic evolution using the Trotter decomposition - evolution = models.AdiabaticEvolution(h0, h1, lambda t: t, dt=1e-1) + evolution = AdiabaticEvolution(h0, h1, lambda t: t, dt=1e-1) final_state = evolution(final_time=1.0) @@ -1842,18 +1890,20 @@ done as follows: .. testcode:: import numpy as np - from qibo import hamiltonians, models + + from qibo.models import AdiabaticEvolution + from qibo.hamiltonians import TFIM, X # Define Hamiltonians - h0 = hamiltonians.X(3) - h1 = hamiltonians.TFIM(3) + h0 = X(3) + h1 = TFIM(3) # Define scheduling function with a free variational parameter ``p`` sp = lambda t, p: (1 - p) * np.sqrt(t) + p * t # Define an evolution model with dt=1e-2 - evolution = models.AdiabaticEvolution(h0, h1, sp, dt=1e-2) + evolution = AdiabaticEvolution(h0, h1, sp, dt=1e-2) # Find the optimal value for ``p`` starting from ``p = 0.5`` and ``T=1``. initial_guess = [0.5, 1] - # best, params, extra = evolution.minimize(initial_guess, method="BFGS", options={'disp': True}) + best, params, extra = evolution.minimize(initial_guess, method="BFGS", options={'disp': True}) print(best) # prints the best energy

found from the final state print(params) # prints the optimal values for the parameters. .. testoutput:: @@ -1891,7 +1941,9 @@ corresponding 16x16 matrix: .. testcode:: import numpy as np - from qibo import hamiltonians, matrices + + from qibo import matrices + from qibo.hamiltonians import Hamiltonian # ZZ terms matrix = np.kron(np.kron(matrices.Z, matrices.Z), np.kron(matrices.I, matrices.I)) @@ -1904,7 +1956,7 @@ corresponding 16x16 matrix: matrix += np.kron(np.kron(matrices.I, matrices.I), np.kron(matrices.X, matrices.I)) matrix += np.kron(np.kron(matrices.I, matrices.I), np.kron(matrices.I, matrices.X)) # Create Hamiltonian object - ham = hamiltonians.Hamiltonian(4, matrix) + ham = Hamiltonian(4, matrix) Although it is possible to generalize the above construction to arbitrary number @@ -1924,7 +1976,7 @@ For example, the TFIM on four qubits could be constructed as: .. testcode:: import numpy as np - from qibo import hamiltonians + from qibo.hamiltonians import SymbolicHamiltonian from qibo.symbols import X, Z # Define Hamiltonian using Qibo symbols @@ -1936,7 +1988,7 @@ For example, the TFIM on four qubits could be constructed as: symbolic_ham += sum(X(i) for i in range(4)) # Define a Hamiltonian using the above form - ham = hamiltonians.SymbolicHamiltonian(symbolic_ham) + ham = SymbolicHamiltonian(symbolic_ham) # This Hamiltonian is memory efficient as it does not construct the full matrix # The corresponding dense Hamiltonian which contains the full matrix can @@ -1962,11 +2014,11 @@ constructing each symbol: .. testcode:: - from qibo import hamiltonians + from qibo.hamiltonians import SymbolicHamiltonian from qibo.symbols import Z form = Z(0, commutative=True) * Z(1, commutative=True) + Z(1, commutative=True) * Z(2, commutative=True) - ham = hamiltonians.SymbolicHamiltonian(form) + ham = SymbolicHamiltonian(form) .. _hamexpectation-example: diff --git a/doc/source/code-examples/examples.rst b/doc/source/code-examples/examples.rst index ca5bdc190c..8e3b849a38 100644 --- a/doc/source/code-examples/examples.rst +++ b/doc/source/code-examples/examples.rst @@ -14,14 +14,14 @@ Here is an example of a circuit with 2 qubits: from qibo import Circuit, gates # Construct the circuit - c = Circuit(2) + circuit = Circuit(2) # Add some gates - c.add(gates.H(0)) - c.add(gates.H(1)) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) # Define an initial state (optional - default initial state is |00>) initial_state = np.ones(4) / 2.0 # Execute the circuit and obtain the final state - result = c(initial_state) # c.execute(initial_state) also works + result = circuit(initial_state) # circuit.execute(initial_state) also works print(result.state()) # should print `tf.Tensor([1, 0, 0, 0])` print(result.state()) @@ -44,15 +44,15 @@ evaluation performance, e.g.: qibo.set_backend("tensorflow") from qibo import Circuit, gates - c = Circuit(2) - c.add(gates.X(0)) - c.add(gates.X(1)) - c.add(gates.CU1(0, 1, 0.1234)) - c.compile() + circuit = Circuit(2) + circuit.add(gates.X(0)) + circuit.add(gates.X(1)) + circuit.add(gates.CU1(0, 1, 0.1234)) + circuit.compile() for i in range(100): init_state = np.ones(4) / 2.0 + i - c(init_state) + circuit(init_state) Note that compiling is only supported when the native ``tensorflow`` backend is used. This backend is much slower than ``qibojit`` which uses custom operators @@ -72,14 +72,14 @@ For example from qibo import Circuit, gates - c = Circuit(3) - c.add(gates.H(0)) - c.add(gates.H(1)) - c.add(gates.CNOT(0, 2)) - c.add(gates.CNOT(1, 2)) - c.add(gates.H(2)) - c.add(gates.TOFFOLI(0, 1, 2)) - print(c.summary()) + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.CNOT(1, 2)) + circuit.add(gates.H(2)) + circuit.add(gates.TOFFOLI(0, 1, 2)) + print(circuit.summary()) # Prints ''' Circuit depth = 5 @@ -111,23 +111,23 @@ For example for the circuit of the previous example: from qibo import Circuit, gates - c = Circuit(3) - c.add(gates.H(0)) - c.add(gates.H(1)) - c.add(gates.CNOT(0, 2)) - c.add(gates.CNOT(1, 2)) - c.add(gates.H(2)) - c.add(gates.TOFFOLI(0, 1, 2)) + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.CNOT(1, 2)) + circuit.add(gates.H(2)) + circuit.add(gates.TOFFOLI(0, 1, 2)) .. testcode:: - common_gates = c.gate_names.most_common() + common_gates = circuit.gate_names.most_common() # returns the list [("h", 3), ("cx", 2), ("ccx", 1)] most_common_gate = common_gates[0][0] # returns "h" - all_h_gates = c.gates_of_type(gates.H) + all_h_gates = circuit.gates_of_type(gates.H) # returns the list [(0, ref to H(0)), (1, ref to H(1)), (4, ref to H(2))] A circuit may contain multi-controlled or other gates that are not supported by @@ -158,12 +158,12 @@ information about the measured samples. For example from qibo import Circuit, gates - c = Circuit(2) - c.add(gates.X(0)) + circuit = Circuit(2) + circuit.add(gates.X(0)) # Add a measurement register on both qubits - c.add(gates.M(0, 1)) + circuit.add(gates.M(0, 1)) # Execute the circuit with the default initial state |00>. - result = c(nshots=100) + result = circuit(nshots=100) Measurements are now accessible using the ``samples`` and ``frequencies`` methods on the ``result`` object. In particular @@ -185,12 +185,12 @@ during the addition of measurement gates in the circuit. For example from qibo import Circuit, gates - c = Circuit(5) - c.add(gates.X(0)) - c.add(gates.X(4)) - c.add(gates.M(0, 1, register_name="A")) - c.add(gates.M(3, 4, register_name="B")) - result = c(nshots=100) + circuit = Circuit(5) + circuit.add(gates.X(0)) + circuit.add(gates.X(4)) + circuit.add(gates.M(0, 1, register_name="A")) + circuit.add(gates.M(3, 4, register_name="B")) + result = circuit(nshots=100) creates a circuit with five qubits that has two registers: ``A`` consisting of qubits ``0`` and ``1`` and ``B`` consisting of qubits ``3`` and ``4``. Here @@ -309,8 +309,8 @@ For example from qibo.models import QFT - c = QFT(5) - c.draw() + circuit = QFT(5) + circuit.draw() # Prints ''' q0: ─H─U1─U1─U1─U1───────────────────────────x─── @@ -348,15 +348,15 @@ For example, we can draw the QFT circuit for 5-qubits: from qibo.ui import plot_circuit # create a 5-qubits QFT circuit - c = QFT(5) - c.add(gates.M(qubit) for qubit in range(2)) + circuit = QFT(5) + circuit.add(gates.M(qubit) for qubit in range(2)) # print circuit with default options (default black & white style, scale factor of 0.6 and clustered gates) - plot_circuit(c); + plot_circuit(circuit); # print the circuit with built-int style "garnacha", clustering gates and a custom scale factor # built-in styles: "garnacha", "fardelejo", "quantumspain", "color-blind", "cachirulo" or custom dictionary - plot_circuit(c, scale = 0.8, cluster_gates = True, style="garnacha"); + plot_circuit(circuit, scale = 0.8, cluster_gates = True, style="garnacha"); # plot the Qibo circuit with a custom style custom_style = { @@ -369,4 +369,4 @@ For example, we can draw the QFT circuit for 5-qubits: "controlcolor" : "#360000" } - plot_circuit(c, scale = 0.8, cluster_gates = True, style=custom_style); + plot_circuit(circuit, scale = 0.8, cluster_gates = True, style=custom_style); diff --git a/doc/source/getting-started/quickstart.rst b/doc/source/getting-started/quickstart.rst index d5e65db239..c08287d9f9 100644 --- a/doc/source/getting-started/quickstart.rst +++ b/doc/source/getting-started/quickstart.rst @@ -40,11 +40,11 @@ Here an example of adding gates and measurements: import numpy as np from qibo import Circuit, gates - c = Circuit(2) - c.add(gates.X(0)) + circuit = Circuit(2) + circuit.add(gates.X(0)) # Add a measurement register on both qubits - c.add(gates.M(0, 1)) + circuit.add(gates.M(0, 1)) # Execute the circuit with the default initial state |00>. - result = c(nshots=100) + result = circuit(nshots=100) diff --git a/examples/3_tangle/canonizator.py b/examples/3_tangle/canonizator.py index 264ef8ccb6..70c53fcef4 100644 --- a/examples/3_tangle/canonizator.py +++ b/examples/3_tangle/canonizator.py @@ -29,27 +29,31 @@ def ansatz(p=0): return C -def cost_function(theta, state, circuit, shots=1000): - """Cost function encoding the difference between a state and its up-to-phases canonical form +def cost_function(theta, state, circuit, shots: int = 1000): + """Cost function encoding the difference between a state and its up-to-phases canonical form. + Args: - theta (array): parameters of the unitary rotations. - state (cplx array): three-qubit random state. - circuit (models.Circuit): Qibo variational circuit. - shots (int): Shots used for measuring every circuit. + theta (ndarray): parameters of the unitary rotations. + state (ndarray): three-qubit random state. + circuit (:class:`qibo.models.Circuit`): variational circuit. + shots (int, optional): shots used for measuring every circuit. Defaults to :math:`1000`. + Returns: - float, cost function + float: Cost function """ circuit.set_parameters(theta) measurements = circuit(state, nshots=shots).frequencies(binary=False) return (measurements[1] + measurements[2] + measurements[3]) / shots -def canonize(state, circuit, shots=1000): - """Function to transform a given state into its up-to-phases canonical form +def canonize(state, circuit, shots: int = 1000): + """Function to transform a given state into its up-to-phases canonical form. + Args: - state (cplx array): three-qubit random state. - circuit (models.Circuit): Qibo variational circuit. - shots (int): Shots used for measuring every circuit. + state (ndarray): three-qubit random state. + circuit (:class:`qibo.models.Circuit`): variational circuit. + shots (int): shots used for measuring every circuit. Defaults to :math:`1000`. + Returns: Value cost function, parameters to canonize the given state """ @@ -60,16 +64,20 @@ def canonize(state, circuit, shots=1000): return result.fun, result.x -def canonical_tangle(state, theta, circuit, shots=1000, post_selection=True): - """Tangle of a canonized quantum state +def canonical_tangle( + state, theta, circuit, shots: int = 1000, post_selection: bool = True +): + """Tangle of a canonized quantum state. + Args: - state (cplx array): three-qubit random state. + state (ndarray): three-qubit random state. theta (array): parameters of the unitary rotations. - circuit (models.Circuit): Qibo variational circuit. - shots (int): Shots used for measuring every circuit. - post_selection (bool): whether post selection is applied or not + circuit (:class:`qibo.models.Circuit`): variational circuit. + shots (int, optional): shots used for measuring every circuit. Defaults to :math:`1000`. + post_selection (bool, optional): whether post selection is applied or not + Returns: - tangle + float: tangle """ circuit.set_parameters(theta) result = circuit(state, nshots=shots).frequencies(binary=False) diff --git a/examples/EF_QAE/main.py b/examples/EF_QAE/main.py index 44774fc4e1..ef477c8ec4 100644 --- a/examples/EF_QAE/main.py +++ b/examples/EF_QAE/main.py @@ -3,9 +3,10 @@ import numpy as np from scipy.optimize import minimize -from sklearn.datasets import load_digits +from sklearn.datasets import load_digits # type: ignore -from qibo import gates, hamiltonians, models +from qibo import Circuit, gates +from qibo.hamiltonians import TFIM, Hamiltonian, Z def main(layers, autoencoder, example, maxiter): @@ -18,9 +19,9 @@ def encoder_hamiltonian_simple(nqubits, ncompress): Returns: Encoding Hamiltonian. """ - m0 = hamiltonians.Z(ncompress).matrix + m0 = Z(ncompress).matrix m1 = np.eye(2 ** (nqubits - ncompress), dtype=m0.dtype) - ham = hamiltonians.Hamiltonian(nqubits, np.kron(m1, m0)) + ham = Hamiltonian(nqubits, np.kron(m1, m0)) return 0.5 * (ham + ncompress) def rotate(theta, x): @@ -48,11 +49,11 @@ def rotate(theta, x): ising_groundstates = [] lambdas = np.linspace(0.5, 1.0, 20) for lamb in lambdas: - ising_ham = -1 * hamiltonians.TFIM(nqubits, h=lamb) + ising_ham = -1 * TFIM(nqubits, h=lamb) ising_groundstates.append(ising_ham.eigenvectors()[0]) if autoencoder == 1: - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=0)) @@ -112,7 +113,7 @@ def cost_function_QAE_Ising(params, count): ) elif autoencoder == 0: - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=0)) @@ -191,7 +192,7 @@ def cost_function_EF_QAE_Ising(params, count): ) if autoencoder == 1: - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=0)) @@ -252,7 +253,7 @@ def cost_function_QAE_Digits(params, count): ) elif autoencoder == 0: - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=0)) diff --git a/examples/aavqe/main.py b/examples/aavqe/main.py index 083b76dbb0..586c0bbed3 100644 --- a/examples/aavqe/main.py +++ b/examples/aavqe/main.py @@ -3,11 +3,13 @@ import numpy as np -from qibo import gates, hamiltonians, models +from qibo import Circuit, gates +from qibo.hamiltonians import XXZ, X +from qibo.models.variational import AAVQE def main(nqubits, layers, maxsteps, T_max): - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): circuit.add(gates.RY(q, theta=0) for q in range(nqubits)) circuit.add(gates.CZ(q, q + 1) for q in range(0, nqubits - 1, 2)) @@ -15,10 +17,10 @@ def main(nqubits, layers, maxsteps, T_max): circuit.add(gates.CZ(q, q + 1) for q in range(1, nqubits - 2, 2)) circuit.add(gates.CZ(0, nqubits - 1)) circuit.add(gates.RY(q, theta=0) for q in range(nqubits)) - problem_hamiltonian = hamiltonians.XXZ(nqubits) - easy_hamiltonian = hamiltonians.X(nqubits) + problem_hamiltonian = XXZ(nqubits) + easy_hamiltonian = X(nqubits) s = lambda t: t - aavqe = models.variational.AAVQE( + aavqe = AAVQE( circuit, easy_hamiltonian, problem_hamiltonian, s, nsteps=maxsteps, t_max=T_max ) diff --git a/examples/adiabatic_qml/qaml_scripts/rotational_circuit.py b/examples/adiabatic_qml/qaml_scripts/rotational_circuit.py index 384bb1cdc4..dde1a0d17c 100644 --- a/examples/adiabatic_qml/qaml_scripts/rotational_circuit.py +++ b/examples/adiabatic_qml/qaml_scripts/rotational_circuit.py @@ -4,8 +4,7 @@ from qaml_scripts.evolution import generate_schedule from scipy.integrate import quad -import qibo -from qibo.noise import DepolarizingError, NoiseModel +from qibo import Circuit, gates class rotational_circuit: @@ -203,26 +202,26 @@ def derivative_rotation_angles(self, t): def rotations_circuit(self, t): psi, theta, phi = self.rotation_angles(t) - c = qibo.models.Circuit(self.nqubits, density_matrix=True) + c = Circuit(self.nqubits, density_matrix=True) # H gate - c.add(qibo.gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) - c.add(qibo.gates.RX(q=self.q, theta=np.pi / 2, trainable=False)) - c.add(qibo.gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) + c.add(gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) + c.add(gates.RX(q=self.q, theta=np.pi / 2, trainable=False)) + c.add(gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) # RZ(psi) - c.add(qibo.gates.RZ(q=self.q, theta=psi)) + c.add(gates.RZ(q=self.q, theta=psi)) # RX(theta) - c.add(qibo.gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) - c.add(qibo.gates.RX(q=self.q, theta=-np.pi / 2, trainable=False)) - c.add(qibo.gates.RZ(q=self.q, theta=-theta)) - c.add(qibo.gates.RX(q=self.q, theta=np.pi / 2, trainable=False)) + c.add(gates.RZ(q=self.q, theta=np.pi / 2, trainable=False)) + c.add(gates.RX(q=self.q, theta=-np.pi / 2, trainable=False)) + c.add(gates.RZ(q=self.q, theta=-theta)) + c.add(gates.RX(q=self.q, theta=np.pi / 2, trainable=False)) # RZ(phi) - c.add(qibo.gates.RZ(q=self.q, theta=phi)) + c.add(gates.RZ(q=self.q, theta=phi)) - c.add(qibo.gates.M(self.q)) + c.add(gates.M(self.q)) return c diff --git a/examples/anomaly_detection/test.py b/examples/anomaly_detection/test.py index 88997b6d09..86e1c2ff7c 100644 --- a/examples/anomaly_detection/test.py +++ b/examples/anomaly_detection/test.py @@ -4,8 +4,7 @@ import matplotlib.pyplot as plt import numpy as np -import qibo -from qibo import Circuit, gates +from qibo import Circuit, gates, get_backend, set_backend LOCAL_FOLDER = Path(__file__).parent @@ -21,8 +20,8 @@ def main(n_layers, train_size, filename, plot, save_loss): save_loss (bool): save losses for standard and anomalous data (default False). """ - qibo.set_backend(backend="qiboml", platform="tensorflow") - tf = qibo.get_backend().tf + set_backend(backend="qiboml", platform="tensorflow") + tf = get_backend().tf # Circuit ansatz def make_encoder(n_qubits, n_layers, params, q_compression): @@ -35,7 +34,7 @@ def make_encoder(n_qubits, n_layers, params, q_compression): q_compression (int): number of compressed qubits. Returns: - encoder (qibo.models.Circuit): variational quantum circuit. + :class:`qibo.models.Circuit`: Variational quantum circuit. """ index = 0 @@ -62,11 +61,11 @@ def compute_loss_test(encoder, vector): """Evaluate loss function for one test sample. Args: - encoder (qibo.models.Circuit): variational quantum circuit (trained). - vector (tf.Tensor): test sample, in the form of 1d vector. + encoder (:class:`qibo.models.Circuit`): variational quantum circuit (trained). + vector (:class:`tf.Tensor`): test sample, in the form of 1d vector. Returns: - loss (tf.Variable): loss of the test sample. + :class:`tf.Variable`: Loss of the test sample. """ reconstructed = encoder(vector) # 3 qubits compression diff --git a/examples/anomaly_detection/train.py b/examples/anomaly_detection/train.py index f77377abb5..8fe879946a 100644 --- a/examples/anomaly_detection/train.py +++ b/examples/anomaly_detection/train.py @@ -36,7 +36,7 @@ def make_encoder(n_qubits, n_layers, params, q_compression): q_compression (int): number of compressed qubits. Returns: - encoder (qibo.models.Circuit): variational quantum circuit. + :class:`qibo.models.Circuit`: Variational quantum circuit. """ index = 0 @@ -64,12 +64,12 @@ def compute_loss(encoder, params, vector): """Evaluate loss function for one train sample. Args: - encoder (qibo.models.Circuit): variational quantum circuit. - params (tf.Variable): parameters of the circuit. - vector (tf.Tensor): train sample, in the form of 1d vector. + encoder (:class:`qibo.models.Circuit`): variational quantum circuit. + params (:class:`tf.Variable`): parameters of the circuit. + vector (:class:`tf.Tensor`): train sample, in the form of 1d vector. Returns: - loss (tf.Variable): loss of the training sample. + :class:`tf.Variable`: Loss of the training sample. """ encoder.set_parameters(params) @@ -89,12 +89,12 @@ def train_step(batch_size, encoder, params, dataset): Args: batch_size (int): number of samples in one training batch. - encoder (qibo.models.Circuit): variational quantum circuit. - params (tf.Variable): parameters of the circuit. - vector (tf.Tensor): train sample, in the form of 1d vector. + encoder (:class:`qibo.models.Circuit`): variational quantum circuit. + params (:class:`tf.Variable`): parameters of the circuit. + vector (:class:`tf.Tensor`): train sample, in the form of 1d vector. Returns: - loss (tf.Variable): average loss of the training batch. + :class:`tf.Variable`: Average loss of the training batch. """ loss = 0.0 diff --git a/examples/autoencoder/main.py b/examples/autoencoder/main.py index 024d7e94ba..87779a6d25 100644 --- a/examples/autoencoder/main.py +++ b/examples/autoencoder/main.py @@ -4,7 +4,8 @@ import numpy as np from scipy.optimize import minimize -from qibo import gates, hamiltonians, models +from qibo import Circuit, gates +from qibo.hamiltonians import TFIM, Hamiltonian, Z def main(nqubits, layers, compress, lambdas, maxiter): @@ -17,9 +18,9 @@ def encoder_hamiltonian_simple(nqubits, ncompress): Returns: Encoding Hamiltonian. """ - m0 = hamiltonians.Z(ncompress).matrix + m0 = Z(ncompress).matrix m1 = np.eye(2 ** (nqubits - ncompress), dtype=m0.dtype) - ham = hamiltonians.Hamiltonian(nqubits, np.kron(m1, m0)) + ham = Hamiltonian(nqubits, np.kron(m1, m0)) return 0.5 * (ham + ncompress) def cost_function(params, count): @@ -31,7 +32,7 @@ def cost_function(params, count): Returns: Value of the cost function. """ - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=0)) @@ -65,7 +66,7 @@ def cost_function(params, count): ising_groundstates = [] for lamb in lambdas: - ising_ham = -1 * hamiltonians.TFIM(nqubits, h=lamb) + ising_ham = -1 * TFIM(nqubits, h=lamb) ising_groundstates.append(ising_ham.eigenvectors()[0]) count = [0] diff --git a/examples/bell-variational/functions.py b/examples/bell-variational/functions.py index 4dbce4c530..3a123d5bb7 100644 --- a/examples/bell-variational/functions.py +++ b/examples/bell-variational/functions.py @@ -5,23 +5,24 @@ def bell_circuit(basis): """Create a Bell circuit with a distinct measurement basis and parametrizable gates. + Args: basis (str): '00', '01, '10', '11' where a '1' marks a measurement in the X basis - and a '0' a measurement in the Z basis. + and a '0' a measurement in the Z basis. Returns: :class:`qibo.core.circuit.Circuit` """ - c = Circuit(2) - c.add(gates.RY(0, theta=0)) - c.add(gates.CNOT(0, 1)) - c.add(gates.RY(0, theta=0)) + circuit = Circuit(2) + circuit.add(gates.RY(0, theta=0)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.RY(0, theta=0)) for a in range(2): if basis[a] == "1": - c.add(gates.H(a)) - c.add(gates.M(*range(2))) - return c + circuit.add(gates.H(a)) + circuit.add(gates.M(*range(2))) + return circuit def set_parametrized_circuits(): diff --git a/examples/benchmarks/circuits.py b/examples/benchmarks/circuits.py index cdcd1444a6..57e5c09518 100644 --- a/examples/benchmarks/circuits.py +++ b/examples/benchmarks/circuits.py @@ -1,6 +1,7 @@ import numpy as np -from qibo import gates, models +from qibo import Circuit, gates +from qibo.models import QFT def VariationalCircuit(nqubits, nlayers=1, theta_values=None): @@ -64,10 +65,10 @@ def PrepareGHZ(nqubits): def CircuitFactory(nqubits, circuit_name, accelerators=None, **kwargs): if circuit_name == "qft": - circuit = models.QFT(nqubits, accelerators=accelerators) + circuit = QFT(nqubits, accelerators=accelerators) else: if circuit_name not in _CIRCUITS: raise KeyError(f"Unknown benchmark circuit type {circuit_name}.") - circuit = models.Circuit(nqubits, accelerators=accelerators) + circuit = Circuit(nqubits, accelerators=accelerators) circuit.add(_CIRCUITS.get(circuit_name)(nqubits, **kwargs)) return circuit diff --git a/examples/benchmarks/vqe.py b/examples/benchmarks/vqe.py index 2b345b044b..4e0d5c5ffc 100644 --- a/examples/benchmarks/vqe.py +++ b/examples/benchmarks/vqe.py @@ -8,8 +8,17 @@ import numpy as np from utils import BenchmarkLogger -import qibo -from qibo import gates, hamiltonians, models +from qibo import ( + Circuit, + gates, + get_backend, + get_device, + get_precision, + get_threads, + set_backend, +) +from qibo.hamiltonians import XXZ +from qibo.models import VQE parser = argparse.ArgumentParser() parser.add_argument("--nqubits", default=6, help="Number of qubits.", type=int) @@ -29,7 +38,7 @@ def create_circuit(nqubits, nlayers): """Creates variational circuit.""" - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(nlayers): circuit.add(gates.RY(q, theta=0) for q in range(nqubits)) circuit.add(gates.CZ(q, q + 1) for q in range(0, nqubits - 1, 2)) @@ -44,17 +53,17 @@ def main( nqubits, nlayers, backend, fuse=False, method="Powell", maxiter=None, filename=None ): """Performs a VQE circuit minimization test.""" - qibo.set_backend(backend) + set_backend(backend) logs = BenchmarkLogger(filename) logs.append( { "nqubits": nqubits, "nlayers": nlayers, "fuse": fuse, - "backend": qibo.get_backend(), - "precision": qibo.get_precision(), - "device": qibo.get_device(), - "threads": qibo.get_threads(), + "backend": get_backend(), + "precision": get_precision(), + "device": get_device(), + "threads": get_threads(), "method": method, "maxiter": maxiter, } @@ -67,8 +76,8 @@ def main( circuit = create_circuit(nqubits, nlayers) if fuse: circuit = circuit.fuse() - hamiltonian = hamiltonians.XXZ(nqubits=nqubits) - vqe = models.VQE(circuit, hamiltonian) + hamiltonian = XXZ(nqubits=nqubits) + vqe = VQE(circuit, hamiltonian) logs[-1]["creation_time"] = time.time() - start_time target = np.real(np.min(hamiltonian.eigenvalues())) diff --git a/examples/circuit-draw-mpl/qibo-draw-circuit-matplotlib.ipynb b/examples/circuit-draw-mpl/qibo-draw-circuit-matplotlib.ipynb index 8fb669cdb4..5a704758c9 100644 --- a/examples/circuit-draw-mpl/qibo-draw-circuit-matplotlib.ipynb +++ b/examples/circuit-draw-mpl/qibo-draw-circuit-matplotlib.ipynb @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "66e4921b-c1ea-479d-9926-d93a7c784be9", "metadata": {}, "outputs": [], @@ -55,8 +55,8 @@ "\n", "# Qibo libraries\n", "import qibo\n", - "from qibo import gates, models\n", - "from qibo.models import Circuit, QFT\n", + "from qibo import Circuit, gates\n", + "from qibo.models import QFT\n", "\n", "# new plot function based on matplotlib\n", "from qibo.ui import plot_circuit\n", @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "eda54008", "metadata": {}, "outputs": [ @@ -86,7 +86,7 @@ "nlayers = 3\n", "\n", "# Create variational ansatz circuit Twolocal\n", - "ansatz = models.Circuit(nqubits)\n", + "ansatz = Circuit(nqubits)\n", "for l in range(nlayers):\n", " \n", " ansatz.add((gates.RY(q, theta=0) for q in range(nqubits)))\n", @@ -156,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "62d00656-b40d-44f1-b56a-6733eeed6759", "metadata": {}, "outputs": [ @@ -171,21 +171,21 @@ } ], "source": [ - "c = models.Circuit(3)\n", - "c.add(gates.H(1))\n", - "c.add(gates.X(1))\n", - "c.add(gates.SX(2))\n", - "c.add(gates.CSX(0,2))\n", - "c.add(gates.TOFFOLI(0,1, 2))\n", - "c.add(gates.CNOT(1, 2))\n", - "c.add(gates.SWAP(1,2))\n", - "c.add(gates.SiSWAP(1,2))\n", - "c.add(gates.FSWAP(1,2))\n", - "c.add(gates.DEUTSCH(1, 0, 2, np.pi))\n", - "c.add(gates.X(1))\n", - "c.add(gates.X(0))\n", - "c.add(gates.M(qubit) for qubit in range(2))\n", - "c.draw()" + "circuit = Circuit(3)\n", + "circuit.add(gates.H(1))\n", + "circuit.add(gates.X(1))\n", + "circuit.add(gates.SX(2))\n", + "circuit.add(gates.CSX(0,2))\n", + "circuit.add(gates.TOFFOLI(0,1, 2))\n", + "circuit.add(gates.CNOT(1, 2))\n", + "circuit.add(gates.SWAP(1,2))\n", + "circuit.add(gates.SiSWAP(1,2))\n", + "circuit.add(gates.FSWAP(1,2))\n", + "circuit.add(gates.DEUTSCH(1, 0, 2, np.pi))\n", + "circuit.add(gates.X(1))\n", + "circuit.add(gates.X(0))\n", + "circuit.add(gates.M(qubit) for qubit in range(2))\n", + "circuit.draw()" ] }, { @@ -211,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "5f5896a5-e639-401c-992a-19b960720ec4", "metadata": {}, "outputs": [ @@ -228,10 +228,10 @@ } ], "source": [ - "c = QFT(5)\n", - "c.add(gates.M(qubit) for qubit in range(2))\n", + "circuit = QFT(5)\n", + "circuit.add(gates.M(qubit) for qubit in range(2))\n", "\n", - "c.draw()" + "circuit.draw()" ] }, { @@ -244,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "afa80613-6330-4a85-928f-4cb884d81990", "metadata": {}, "outputs": [ @@ -260,12 +260,12 @@ } ], "source": [ - "plot_circuit(c, scale = 0.8, cluster_gates = True, style=\"garnacha\");" + "plot_circuit(circuit, scale = 0.8, cluster_gates = True, style=\"garnacha\");" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "916f7b83-1ad7-4984-8573-eb55dfeb125d", "metadata": {}, "outputs": [ @@ -281,12 +281,12 @@ } ], "source": [ - "plot_circuit(c, scale = 0.8, cluster_gates = True, style=\"fardelejo\");" + "plot_circuit(circuit, scale = 0.8, cluster_gates = True, style=\"fardelejo\");" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "b9e1176c-d8dc-47e4-9607-ad24f6f536b9", "metadata": {}, "outputs": [ @@ -302,12 +302,12 @@ } ], "source": [ - "plot_circuit(c, scale = 0.8, cluster_gates = True, style=\"quantumspain\");" + "plot_circuit(circuit, scale = 0.8, cluster_gates = True, style=\"quantumspain\");" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "eaefdf76-af68-4187-996d-bdc9c33a4242", "metadata": {}, "outputs": [ @@ -323,7 +323,7 @@ } ], "source": [ - "plot_circuit(c, scale = 0.8, cluster_gates = True, style=\"color-blind\");" + "plot_circuit(circuit, scale = 0.8, cluster_gates = True, style=\"color-blind\");" ] }, { @@ -367,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "f5077d51", "metadata": {}, "outputs": [ @@ -384,7 +384,7 @@ } ], "source": [ - "c.fuse().draw()" + "circuit.fuse().draw()" ] }, { @@ -449,7 +449,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "626f9d58", "metadata": {}, "outputs": [ @@ -465,18 +465,18 @@ } ], "source": [ - "c = Circuit(2)\n", - "c.add(gates.H(0))\n", - "c.add(gates.CNOT(0,1))\n", - "c.add(gates.M(0,1))\n", + "circuit = Circuit(2)\n", + "circuit.add(gates.H(0))\n", + "circuit.add(gates.CNOT(0,1))\n", + "circuit.add(gates.M(0,1))\n", "\n", "# small dpi value\n", - "plot_circuit(c.fuse(), scale = 0.8, cluster_gates = True, style={\"dpi\": 80});" + "plot_circuit(circuit.fuse(), scale = 0.8, cluster_gates = True, style={\"dpi\": 80});" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "539ef4b7-76fe-4d72-b3f4-78baf44d526d", "metadata": {}, "outputs": [ @@ -493,7 +493,7 @@ ], "source": [ "# higher definition\n", - "plot_circuit(c.fuse(), scale = 0.8, cluster_gates = True, style={\"dpi\": 1000});" + "plot_circuit(circuit.fuse(), scale = 0.8, cluster_gates = True, style={\"dpi\": 1000});" ] }, { diff --git a/examples/qclustering/distance_calc.py b/examples/qclustering/distance_calc.py index 3c4ff780a6..c37f277066 100644 --- a/examples/qclustering/distance_calc.py +++ b/examples/qclustering/distance_calc.py @@ -1,24 +1,19 @@ import math import numpy as np -import util as u from qibo import gates from qibo.models import Circuit def pad_input(X): - """Adds 0s if X log2(X.dim) != round int. + """Add 0s if X log2(X.dim) != round int. - Parameters - ---------- - X : :class:`numpy.ndarray` - Input data + Args: + X (:class:`numpy.ndarray`): Input data. - Returns - ------- - :class:`numpy.ndarray` - Padded X + Returns: + :class:`numpy.ndarray`: Padded X. """ num_features = len(X) if not float(np.log2(num_features)).is_integer(): @@ -30,24 +25,16 @@ def pad_input(X): def DistCalc(a, b, nshots=10000): """Distance calculation using destructive interference. - Parameters - ---------- - a : :class:`numpy.ndarray` - First point - shape = (latent space dimension,) - b : :class:`numpy.ndarray` - Second point - shape = (latent space dimension,) - device_name : str - Name of device for executing a simulation of quantum circuit. - nshots : int - Number of shots for executing a quantum circuit - to get frequencies. - - Returns - ------- - (float, :class:`qibo.models.Circuit`) - (distance, quantum circuit) + Args: + a (:class:`numpy.ndarray`): first point - shape = (latent space dimension,) + b (:class:`numpy.ndarray`): second point - shape = (latent space dimension,) + nshots (int, optional): number of shots for executing a quantum circuit to + get frequencies. Defaults to :math:`10^{4}`. + + Returns: + Tuple(float, :class:`qibo.models.Circuit`): (distance, quantum circuit). """ - num_features = len(a) norm = np.linalg.norm(a - b) a_norm = a / norm b_norm = b / norm diff --git a/examples/qclustering/grover.py b/examples/qclustering/grover.py index 004435f855..c042f63d4c 100644 --- a/examples/qclustering/grover.py +++ b/examples/qclustering/grover.py @@ -9,15 +9,11 @@ def iam_operator(n): """Construct quantum circuit for 'inversion around mean' operator. - Parameters - ---------- - n : int - Number of qubits. - - Returns - ------- - :class:`qibo.models.Circuit` - Quantum circuit. + Args: + n (int): number of qubits. + + Returns: + :class:`qibo.models.Circuit`: quantum circuit. """ qc = Circuit(n) @@ -42,16 +38,14 @@ def iam_operator(n): def grover_qc(qc, n, oracle, n_indices_flip): """Construct quantum circuit for Grover's algorithm. - Parameters - ---------- - qc : :class:`qibo.models.Circuit` - Initial quantum circuit to build up on. - n : int - Number of qubits - oracle : :class:`qibo.models.Circuit`) - Quantum circuit representing an Oracle operator. - n_indices_flip : int - Number of indices which have been selected by Oracle operator. + Args: + qc (:class:`qibo.models.Circuit`): initial quantum circuit to build up on. + n (int): number of qubits. + oracle (:class:`qibo.models.Circuit`): quantum circuit representing an Oracle operator. + n_indices_flip (int): number of indices which have been selected by Oracle operator. + + Returns: + :class:`qibo.models.Circuit`: quantum circuit. """ if n_indices_flip: @@ -65,4 +59,5 @@ def grover_qc(qc, n, oracle, n_indices_flip): qc.add(qc_diff.on_qubits(*(list(range(n))))) # apply inversion around mean qc.add(gates.M(*list(range(n)))) + return qc diff --git a/examples/qfiae/vqregressor_linear_ansatz.py b/examples/qfiae/vqregressor_linear_ansatz.py index 391c9f5ae2..143e479cac 100644 --- a/examples/qfiae/vqregressor_linear_ansatz.py +++ b/examples/qfiae/vqregressor_linear_ansatz.py @@ -139,8 +139,8 @@ def one_prediction(self, this_feature): Returns: circuit's prediction of the output variable, evaluated as difference of probabilities """ - c = self.linear_ansatz(self.params, this_feature) - results = c().probabilities(qubits=[0]) + circuit = self.linear_ansatz(self.params, this_feature) + results = circuit().probabilities(qubits=[0]) res = results[0] - results[1] return res diff --git a/src/qibo/callbacks.py b/src/qibo/callbacks.py index 2313e42e95..315db67429 100644 --- a/src/qibo/callbacks.py +++ b/src/qibo/callbacks.py @@ -70,19 +70,21 @@ class EntanglementEntropy(Callback): Example: .. testcode:: - from qibo import models, gates, callbacks + from qibo import Circuit, gates + from qibo.callbacks import EntanglementEntropy + # create entropy callback where qubit 0 is the first subsystem - entropy = callbacks.EntanglementEntropy([0], compute_spectrum=True) + entropy = EntanglementEntropy([0], compute_spectrum=True) # initialize circuit with 2 qubits and add gates - c = models.Circuit(2) + circuit = Circuit(2) # add callback gates between normal gates - c.add(gates.CallbackGate(entropy)) - c.add(gates.H(0)) - c.add(gates.CallbackGate(entropy)) - c.add(gates.CNOT(0, 1)) - c.add(gates.CallbackGate(entropy)) + circuit.add(gates.CallbackGate(entropy)) + circuit.add(gates.H(0)) + circuit.add(gates.CallbackGate(entropy)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.CallbackGate(entropy)) # execute the circuit - final_state = c() + final_state = circuit() print(entropy[:]) # Should print [0, 0, 1] which is the entanglement entropy # after every gate in the calculation. @@ -237,7 +239,7 @@ class Energy(Callback): object to calculate its expectation value. """ - def __init__(self, hamiltonian: "hamiltonians.Hamiltonian"): + def __init__(self, hamiltonian: "hamiltonians.Hamiltonian"): # type: ignore super().__init__() self.hamiltonian = hamiltonian diff --git a/src/qibo/gates/abstract.py b/src/qibo/gates/abstract.py index a2919c9f95..7b24407048 100644 --- a/src/qibo/gates/abstract.py +++ b/src/qibo/gates/abstract.py @@ -36,7 +36,7 @@ class Gate: Attributes: name (str): Name of the gate. draw_label (str): Optional label for drawing the gate in a circuit - with :func:`qibo.models.Circuit.draw`. + with :meth:`qibo.models.Circuit.draw`. is_controlled_by (bool): ``True`` if the gate was created using the :meth:`qibo.gates.abstract.Gate.controlled_by` method, otherwise ``False``. @@ -263,14 +263,14 @@ def on_qubits(self, qubit_map) -> "Gate": .. testcode:: - from qibo import models, gates - c = models.Circuit(4) + from qibo import Circuit, gates + circuit = Circuit(4) # Add some CNOT gates - c.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3})) # equivalent to gates.CNOT(2, 3) - c.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0})) # equivalent to gates.CNOT(3, 0) - c.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3})) # equivalent to gates.CNOT(1, 3) - c.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1})) # equivalent to gates.CNOT(2, 1) - c.draw() + circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3})) # equivalent to gates.CNOT(2, 3) + circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0})) # equivalent to gates.CNOT(3, 0) + circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3})) # equivalent to gates.CNOT(1, 3) + circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1})) # equivalent to gates.CNOT(2, 1) + circuit.draw() .. testoutput:: q0: ───X───── diff --git a/src/qibo/gates/measurements.py b/src/qibo/gates/measurements.py index bc2c375dc3..0e2ccec754 100644 --- a/src/qibo/gates/measurements.py +++ b/src/qibo/gates/measurements.py @@ -48,8 +48,8 @@ def __init__( register_name: Optional[str] = None, collapse: bool = False, basis: Union[Gate, str] = Z, - p0: Optional["ProbsType"] = None, - p1: Optional["ProbsType"] = None, + p0: Optional["ProbsType"] = None, # type: ignore + p1: Optional["ProbsType"] = None, # type: ignore ): super().__init__() self.name = "measure" @@ -119,7 +119,7 @@ def raw(self) -> dict: @staticmethod def _get_bitflip_tuple( - qubits: Tuple[int, ...], probs: "ProbsType" + qubits: Tuple[int, ...], probs: "ProbsType" # type: ignore ) -> Tuple[float, ...]: if isinstance(probs, float): if probs < 0 or probs > 1: # pragma: no cover @@ -146,7 +146,7 @@ def _get_bitflip_tuple( raise_error(TypeError, f"Invalid type {probs} of bitflip map.") - def _get_bitflip_map(self, p: Optional["ProbsType"] = None) -> Dict[int, float]: + def _get_bitflip_map(self, p: Optional["ProbsType"] = None) -> Dict[int, float]: # type: ignore """Creates dictionary with bitflip probabilities.""" if p is None: return {q: 0 for q in self.qubits} @@ -163,8 +163,8 @@ def add(self, gate): """Adds target qubits to a measurement gate. This method is only used for creating the global measurement gate used - by the `models.Circuit`. - The user is not supposed to use this method and a `ValueError` is + by the :class:`qibo.models.Circuit`. + The user is not supposed to use this method and a ``ValueError`` is raised if he does so. Args: @@ -232,22 +232,24 @@ def on_qubits(self, qubit_map) -> "Gate": and preserving the measurement result register. Args: - qubit_map (int): Dictionary mapping original qubit indices to new ones. + qubit_map (dict): dictionary mapping original qubit indices to new ones. Returns: - A :class:`qibo.gates.Gate.M` object of the original gate - type targeting the given qubits. + :class:`qibo.gates.Gate.M`: object of the original gate type targeting + the given qubits. Example: .. testcode:: - from qibo import models, gates + from qibo import Circuit, gates + measurement = gates.M(0, 1) - c = models.Circuit(3) - c.add(measurement.on_qubits({0: 0, 1: 2})) - assert c.queue[0].result is measurement.result - c.draw() + + circuit = Circuit(3) + circuit.add(measurement.on_qubits({0: 0, 1: 2})) + assert circuit.queue[0].result is measurement.result + circuit.draw() .. testoutput:: q0: ─M─ diff --git a/src/qibo/hamiltonians/adiabatic.py b/src/qibo/hamiltonians/adiabatic.py index d436861c2d..4b7b6512e0 100644 --- a/src/qibo/hamiltonians/adiabatic.py +++ b/src/qibo/hamiltonians/adiabatic.py @@ -131,7 +131,7 @@ def circuit(self, dt, accelerators=None, t=0): t (float): Time that the Hamiltonian should be calculated. Returns: - A :class:`qibo.models.Circuit` implementing the Trotterized evolution. + :class:`qibo.models.Circuit`: Circuit implementing the Trotterized evolution. """ from qibo import Circuit # pylint: disable=import-outside-toplevel from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 9b3916b3ae..4e65259719 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -228,7 +228,7 @@ def _distributed_init(self, nqubits, accelerators): # pragma: no cover accelerators = {'/GPU:0': 2, '/GPU:1': 2} # Define a circuit on 32 qubits to be run in the above GPUs keeping # the full state vector in the CPU memory. - c = Circuit(32, accelerators) + circuit = Circuit(32, accelerators) Args: nqubits (int): Total number of qubits in the circuit. @@ -345,16 +345,17 @@ def on_qubits(self, *qubits): .. testcode:: - from qibo import gates, models + from qibo import Circuit, gates + # create small circuit on 4 qubits - smallc = models.Circuit(4) - smallc.add((gates.RX(i, theta=0.1) for i in range(4))) - smallc.add((gates.CNOT(0, 1), gates.CNOT(2, 3))) + small_circuit = Circuit(4) + small_circuit.add(gates.RX(i, theta=0.1) for i in range(4)) + small_circuit.add((gates.CNOT(0, 1), gates.CNOT(2, 3))) # create large circuit on 8 qubits - largec = models.Circuit(8) - largec.add((gates.RY(i, theta=0.1) for i in range(8))) + large_circuit = Circuit(8) + large_circuit.add(gates.RY(i, theta=0.1) for i in range(8)) # add the small circuit to the even qubits of the large one - largec.add(smallc.on_qubits(*range(0, 8, 2))) + large_circuit.add(small_circuit.on_qubits(*range(0, 8, 2))) """ if len(qubits) != self.nqubits: raise_error( @@ -384,7 +385,7 @@ def light_cone(self, *qubits): qubits (int): Qubit ids that the observable has support on. Returns: - circuit (qibo.models.Circuit): Circuit that contains only + circuit (:class:`qibo.models.Circuit`): Circuit that contains only the qubits that are required for calculating expectation involving the given observable qubits. qubit_map (dict): Dictionary mapping the qubit ids of the original @@ -554,22 +555,22 @@ def with_pauli_noise(self, noise_map: NoiseMapType): from qibo import Circuit, gates # use density matrices for noise simulation - c = Circuit(2, density_matrix=True) - c.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)]) + circuit = Circuit(2, density_matrix=True) + circuit.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)]) noise_map = { 0: list(zip(["X", "Z"], [0.1, 0.2])), 1: list(zip(["Y", "Z"], [0.2, 0.1])) } - noisy_c = c.with_pauli_noise(noise_map) - # ``noisy_c`` will be equivalent to the following circuit - c2 = Circuit(2, density_matrix=True) - c2.add(gates.H(0)) - c2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Z", 0.2)])) - c2.add(gates.H(1)) - c2.add(gates.PauliNoiseChannel(1, [("Y", 0.2), ("Z", 0.1)])) - c2.add(gates.CNOT(0, 1)) - c2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Z", 0.2)])) - c2.add(gates.PauliNoiseChannel(1, [("Y", 0.2), ("Z", 0.1)])) + noisy_circuit = circuit.with_pauli_noise(noise_map) + # ``noisy_circuit`` will be equivalent to the following circuit + circuit_2 = Circuit(2, density_matrix=True) + circuit_2.add(gates.H(0)) + circuit_2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Z", 0.2)])) + circuit_2.add(gates.H(1)) + circuit_2.add(gates.PauliNoiseChannel(1, [("Y", 0.2), ("Z", 0.1)])) + circuit_2.add(gates.CNOT(0, 1)) + circuit_2.add(gates.PauliNoiseChannel(0, [("X", 0.1), ("Z", 0.2)])) + circuit_2.add(gates.PauliNoiseChannel(1, [("Y", 0.2), ("Z", 0.1)])) """ if self.accelerators: # pragma: no cover raise_error( @@ -800,23 +801,26 @@ def set_parameters(self, parameters): from qibo import Circuit, gates # create a circuit with all parameters set to 0. - c = Circuit(3) - c.add(gates.RX(0, theta=0)) - c.add(gates.RY(1, theta=0)) - c.add(gates.CZ(1, 2)) - c.add(gates.fSim(0, 2, theta=0, phi=0)) - c.add(gates.H(2)) + circuit = Circuit(3) + circuit.add(gates.RX(0, theta=0)) + circuit.add(gates.RY(1, theta=0)) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.fSim(0, 2, theta=0, phi=0)) + circuit.add(gates.H(2)) # set new values to the circuit's parameters using list params = [0.123, 0.456, (0.789, 0.321)] - c.set_parameters(params) + circuit.set_parameters(params) # or using dictionary - params = {c.queue[0]: 0.123, c.queue[1]: 0.456, - c.queue[3]: (0.789, 0.321)} - c.set_parameters(params) - # or using flat list (or an equivalent `np.array`/`tf.Tensor`) + params = { + circuit.queue[0]: 0.123, + circuit.queue[1]: 0.456, + circuit.queue[3]: (0.789, 0.321) + } + circuit.set_parameters(params) + # or using flat list (or an equivalent `np.array`/`tf.Tensor`/`torch.Tensor`) params = [0.123, 0.456, 0.789, 0.321] - c.set_parameters(params) + circuit.set_parameters(params) """ from collections.abc import Iterable @@ -919,15 +923,15 @@ def summary(self) -> str: from qibo import Circuit, gates - c = Circuit(3) - c.add(gates.H(0)) - c.add(gates.H(1)) - c.add(gates.CNOT(0, 2)) - c.add(gates.CNOT(1, 2)) - c.add(gates.H(2)) - c.add(gates.TOFFOLI(0, 1, 2)) + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.CNOT(1, 2)) + circuit.add(gates.H(2)) + circuit.add(gates.TOFFOLI(0, 1, 2)) - print(c.summary()) + print(circuit.summary()) # Prints ''' Circuit depth = 5 @@ -977,14 +981,15 @@ def fuse(self, max_qubits=2): Example: .. testcode:: - from qibo import gates, models - c = models.Circuit(2) - c.add([gates.H(0), gates.H(1)]) - c.add(gates.CNOT(0, 1)) - c.add([gates.Y(0), gates.Y(1)]) + from qibo import Circuit, gates + + circuit = Circuit(2) + circuit.add([gates.H(0), gates.H(1)]) + circuit.add(gates.CNOT(0, 1)) + circuit.add([gates.Y(0), gates.Y(1)]) # create circuit with fused gates - fused_c = c.fuse() - # now ``fused_c`` contains a single ``FusedGate`` that is + fused_circuit = circuit.fuse() + # now ``fused_circuit`` contains a single ``FusedGate`` that is # equivalent to applying the five original gates """ if self.accelerators: # pragma: no cover @@ -1207,19 +1212,19 @@ def from_qasm(cls, qasm_code, accelerators=None, density_matrix=False): .. testcode:: - from qibo import gates, models + from qibo import Circuit, gates qasm_code = '''OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; h q[0]; h q[1]; cx q[0],q[1];''' - c = models.Circuit.from_qasm(qasm_code) + circuit = Circuit.from_qasm(qasm_code) # is equivalent to creating the following circuit - c2 = models.Circuit(2) - c2.add(gates.H(0)) - c2.add(gates.H(1)) - c2.add(gates.CNOT(0, 1)) + circuit_2 = Circuit(2) + circuit_2.add(gates.H(0)) + circuit_2.add(gates.H(1)) + circuit_2.add(gates.CNOT(0, 1)) """ parser = QASMParser() return parser.to_circuit(qasm_code, accelerators, density_matrix) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 7aef4db5b9..875bf03b52 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -821,7 +821,7 @@ def get_expectation_val_with_readout_mitigation( Applies readout error mitigation to the given circuit and observable. Args: - circuit (qibo.models.Circuit): input circuit. + circuit (:class:`qibo.models.Circuit`): input circuit. observable (:class:`qibo.hamiltonians.Hamiltonian/:class:`qibo.hamiltonians.SymbolicHamiltonian`): The observable to be measured. noise_model (qibo.models.noise.Noise, optional): the noise model to be applied. Defaults to ``None``. nshots (int, optional): the number of shots for the circuit execution. Defaults to :math:`10000`. @@ -1163,7 +1163,7 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend Helper function to execute the given circuit with the specified parameters. Args: - circuit (qibo.models.Circuit): input circuit. + circuit (:class:`qibo.models.Circuit`): input circuit. qubit_map (list): the qubit map. If ``None``, a list of range of circuit's qubits is used. Defaults to ``None``. noise_model (qibo.models.noise.Noise, optional): The noise model to be applied. Defaults to ``None``. nshots (int): the number of shots for the circuit execution. Defaults to :math:`10000`.. diff --git a/src/qibo/models/grover.py b/src/qibo/models/grover.py index c18fab512e..91777df112 100644 --- a/src/qibo/models/grover.py +++ b/src/qibo/models/grover.py @@ -118,47 +118,47 @@ def __init__( def initialize(self): """Initialize the Grover algorithm with the superposition and Grover ancilla.""" - c = Circuit(self.nqubits) - c.add(gates.X(self.nqubits - 1)) - c.add(gates.H(self.nqubits - 1)) + circuit = Circuit(self.nqubits) + circuit.add(gates.X(self.nqubits - 1)) + circuit.add(gates.H(self.nqubits - 1)) if self.initial_state_circuit: - c.add( + circuit.add( self.initial_state_circuit.invert().on_qubits( *range(self.initial_state_circuit.nqubits) ) ) - c.add(self.superposition.on_qubits(*self.space_sup)) - return c + circuit.add(self.superposition.on_qubits(*self.space_sup)) + return circuit def diffusion(self): """Construct the diffusion operator out of the superposition circuit.""" nqubits = self.superposition.nqubits + 1 - c = Circuit(nqubits) - c.add(self.superposition.invert().on_qubits(*range(nqubits - 1))) + circuit = Circuit(nqubits) + circuit.add(self.superposition.invert().on_qubits(*range(nqubits - 1))) if self.initial_state_circuit: - c.add( + circuit.add( self.initial_state_circuit.invert().on_qubits( *range(self.initial_state_circuit.nqubits) ) ) - c.add([gates.X(i) for i in range(self.sup_qubits)]) - c.add(gates.X(nqubits - 1).controlled_by(*range(self.sup_qubits))) - c.add([gates.X(i) for i in range(self.sup_qubits)]) + circuit.add([gates.X(i) for i in range(self.sup_qubits)]) + circuit.add(gates.X(nqubits - 1).controlled_by(*range(self.sup_qubits))) + circuit.add([gates.X(i) for i in range(self.sup_qubits)]) if self.initial_state_circuit: - c.add( + circuit.add( self.initial_state_circuit.on_qubits( *range(self.initial_state_circuit.nqubits) ) ) - c.add(self.superposition.on_qubits(*range(nqubits - 1))) - return c + circuit.add(self.superposition.on_qubits(*range(nqubits - 1))) + return circuit def step(self): """Combine oracle and diffusion for a Grover step.""" - c = Circuit(self.nqubits) - c.add(self.oracle.on_qubits(*self.space_ora)) - c.add(self.diffusion().on_qubits(*(self.space_sup + [self.nqubits - 1]))) - return c + circuit = Circuit(self.nqubits) + circuit.add(self.oracle.on_qubits(*self.space_ora)) + circuit.add(self.diffusion().on_qubits(*(self.space_sup + [self.nqubits - 1]))) + return circuit def circuit(self, iterations): """Creates circuit that performs Grover's algorithm with a set amount of iterations. @@ -169,12 +169,12 @@ def circuit(self, iterations): Returns: :class:`qibo.core.circuit.Circuit` that performs Grover's algorithm. """ - c = Circuit(self.nqubits) - c += self.initialize() + circuit = Circuit(self.nqubits) + circuit += self.initialize() for _ in range(iterations): - c += self.step() - c.add(gates.M(*range(self.sup_qubits))) - return c + circuit += self.step() + circuit.add(gates.M(*range(self.sup_qubits))) + return circuit def iterative_grover(self, lamda_value=6 / 5, backend=None): """Iterative approach of Grover for when the number of solutions is not known. diff --git a/src/qibo/models/qcnn.py b/src/qibo/models/qcnn.py index d3a35c2522..7cd4f7a955 100644 --- a/src/qibo/models/qcnn.py +++ b/src/qibo/models/qcnn.py @@ -1,4 +1,6 @@ -""" This module implements a Quantum Convolutional Neural Network (QCNN) for classification tasks. The QCNN model was originally proposed in: arXiv:1810.03787 _ for the identification of quantum phases. +""" Implement a Quantum Convolutional Neural Network (QCNN) for classification tasks. +The QCNN model was originally proposed in: arXiv:1810.03787 _ +for the identification of quantum phases. The QuantumCNN class in this module provides methods to construct the QCNN. """ @@ -14,8 +16,8 @@ class QuantumCNN: """ Model that implements and trains a variational quantum convolutional network (QCNN) for classification tasks. - The QCNN model was originally proposed in: `arXiv:1810.03787 `_ - for the identification of quantum phases. + The QCNN model was originally proposed in: `arXiv:1810.03787 + `_ for the identification of quantum phases. Args: nqubits (int): number of qubits of the input states. Currently supports powers of 2. @@ -23,18 +25,22 @@ class QuantumCNN: nclasses (int): number of classes to be classified. Default setting of 2 (phases). params: initial list of variational parameters. If not provided, all parameters will be initialized to zero. - twoqubitansatz (:class:`qibo.models.Circuit`): a two qubit ansatz that can be input by the user to form the two qubit ansatz used in the convolutional circuit. + twoqubitansatz (:class:`qibo.models.Circuit`): a two qubit ansatz that can be input by + the user to form the two qubit ansatz used in the convolutional circuit. + Example: .. testcode:: import math - import numpy as np import random - import qibo + + import numpy as np + + from qibo import set_backend from qibo.models.qcnn import QuantumCNN + set_backend("numpy") - qibo.set_backend("numpy") data = np.random.rand(16) data = data / np.linalg.norm(data) data = [data] @@ -110,15 +116,15 @@ def quantum_conv_circuit(self, bits, symbols): Returns: Circuit for a single convolutional layer """ - c = Circuit(self.nqubits) + circuit = Circuit(self.nqubits) for first, second in zip(bits[0::2], bits[1::2]): - c += self.two_qubit_unitary([first, second], symbols) + circuit += self.two_qubit_unitary([first, second], symbols) # check that there are more than 2 qubits to prevent double conv if len(bits) > 2: for first, second in zip(bits[1::2], bits[2::2] + [bits[0]]): - c += self.two_qubit_unitary([first, second], symbols) - return c + circuit += self.two_qubit_unitary([first, second], symbols) + return circuit def quantum_pool_circuit(self, source_bits, sink_bits, symbols): """ @@ -131,10 +137,10 @@ def quantum_pool_circuit(self, source_bits, sink_bits, symbols): Returns: Circuit for a single pooling layer """ - c = Circuit(self.nqubits) + circuit = Circuit(self.nqubits) for source, sink in zip(source_bits, sink_bits): - c += self.two_qubit_pool(source, sink, symbols) - return c + circuit += self.two_qubit_pool(source, sink, symbols) + return circuit def ansatz(self, nlayers, params=None): """ @@ -156,24 +162,24 @@ def ansatz(self, nlayers, params=None): nbits = self.nqubits qubits = [_ for _ in range(nbits)] - c = Circuit(self.nqubits) + circuit = Circuit(self.nqubits) for layer in range(nlayers): conv_start = int(nbits - nbits / (2**layer)) pool_start = int(nbits - nbits / (2 ** (layer + 1))) param_start = layer * nparams_layer - c += self.quantum_conv_circuit( + circuit += self.quantum_conv_circuit( qubits[conv_start:], symbols[param_start : param_start + nparams_conv] ) - c += self.quantum_pool_circuit( + circuit += self.quantum_pool_circuit( qubits[conv_start:pool_start], qubits[pool_start:], symbols[param_start + nparams_conv : param_start + nparams_layer], ) # Measurements - c.add(gates.M(*[nbits - 1 - i for i in range(self.measured_qubits)])) + circuit.add(gates.M(*[nbits - 1 - i for i in range(self.measured_qubits)])) - return c + return circuit def one_qubit_unitary(self, bit, symbols): """ @@ -186,12 +192,12 @@ def one_qubit_unitary(self, bit, symbols): Returns: Circuit containing the unitaries added to the specified qubit. """ - c = Circuit(self.nqubits) - c.add(gates.RX(bit, symbols[0])) - c.add(gates.RY(bit, symbols[1])) - c.add(gates.RZ(bit, symbols[2])) + circuit = Circuit(self.nqubits) + circuit.add(gates.RX(bit, symbols[0])) + circuit.add(gates.RY(bit, symbols[1])) + circuit.add(gates.RZ(bit, symbols[2])) - return c + return circuit def two_qubit_unitary(self, bits, symbols): """ @@ -204,23 +210,23 @@ def two_qubit_unitary(self, bits, symbols): Circuit containing the unitaries added to the specified qubits. """ + circuit = Circuit(self.nqubits) + if self.twoqubitansatz is None: - c = Circuit(self.nqubits) - c += self.one_qubit_unitary(bits[0], symbols[0:3]) - c += self.one_qubit_unitary(bits[1], symbols[3:6]) - c.add(gates.RZZ(bits[0], bits[1], symbols[6])) - c.add(gates.RYY(bits[0], bits[1], symbols[7])) - c.add(gates.RXX(bits[0], bits[1], symbols[8])) + circuit += self.one_qubit_unitary(bits[0], symbols[0:3]) + circuit += self.one_qubit_unitary(bits[1], symbols[3:6]) + circuit.add(gates.RZZ(bits[0], bits[1], symbols[6])) + circuit.add(gates.RYY(bits[0], bits[1], symbols[7])) + circuit.add(gates.RXX(bits[0], bits[1], symbols[8])) - c += self.one_qubit_unitary(bits[0], symbols[9:12]) - c += self.one_qubit_unitary(bits[1], symbols[12:]) + circuit += self.one_qubit_unitary(bits[0], symbols[9:12]) + circuit += self.one_qubit_unitary(bits[1], symbols[12:]) else: - c = Circuit(self.nqubits) - c.add(self.twoqubitansatz.on_qubits(bits[0], bits[1])) - c.set_parameters(symbols[0 : self.nparams_conv]) + circuit.add(self.twoqubitansatz.on_qubits(bits[0], bits[1])) + circuit.set_parameters(symbols[0 : self.nparams_conv]) - return c + return circuit def two_qubit_pool(self, source_qubit, sink_qubit, symbols): """ diff --git a/src/qibo/models/qft.py b/src/qibo/models/qft.py index d9426ff4c7..6c4e307f12 100644 --- a/src/qibo/models/qft.py +++ b/src/qibo/models/qft.py @@ -29,12 +29,12 @@ def QFT(nqubits: int, with_swaps: bool = True, accelerators=None, **kwargs) -> C import numpy as np from qibo.models import QFT nqubits = 6 - c = QFT(nqubits) + circuit = QFT(nqubits) # Random normalized initial state vector init_state = np.random.random(2 ** nqubits) + 1j * np.random.random(2 ** nqubits) init_state = init_state / np.sqrt((np.abs(init_state)**2).sum()) # Execute the circuit - final_state = c(init_state) + final_state = circuit(init_state) """ if accelerators is not None: if not with_swaps: diff --git a/src/qibo/models/tsp.py b/src/qibo/models/tsp.py index 250360aa84..7fcfc92439 100644 --- a/src/qibo/models/tsp.py +++ b/src/qibo/models/tsp.py @@ -166,8 +166,8 @@ def prepare_initial_state(self, ordering): An initial state that is used to start TSP QAOA. """ - c = Circuit(len(ordering) ** 2) + circuit = Circuit(len(ordering) ** 2) for i in range(len(ordering)): - c.add(gates.X(int(self.two_to_one[ordering[i], i]))) - result = self.backend.execute_circuit(c) + circuit.add(gates.X(int(self.two_to_one[ordering[i], i]))) + result = self.backend.execute_circuit(circuit) return result.state() diff --git a/src/qibo/models/utils.py b/src/qibo/models/utils.py index dce386af54..da6b13b65f 100644 --- a/src/qibo/models/utils.py +++ b/src/qibo/models/utils.py @@ -11,11 +11,11 @@ def convert_bit_to_energy(hamiltonian, bitstring): make sure the bitstring is of the right length """ n = len(bitstring) - c = Circuit(n) + circuit = Circuit(n) active_bit = [i for i in range(n) if bitstring[i] == "1"] for i in active_bit: - c.add(gates.X(i)) - result = c() # this is an execution result, a quantum state + circuit.add(gates.X(i)) + result = circuit() # this is an execution result, a quantum state return hamiltonian.expectation(result.state()) diff --git a/src/qibo/models/variational.py b/src/qibo/models/variational.py index 7bdc828bac..a855dc95e0 100644 --- a/src/qibo/models/variational.py +++ b/src/qibo/models/variational.py @@ -17,14 +17,18 @@ class VQE: .. testcode:: import numpy as np - from qibo import gates, models, hamiltonians + + from qibo import Circuit, gates + from qibo.hamiltonians import XXZ + from qibo.models import VQE + # create circuit ansatz for two qubits - circuit = models.Circuit(2) + circuit = Circuit(2) circuit.add(gates.RY(0, theta=0)) # create XXZ Hamiltonian for two qubits - hamiltonian = hamiltonians.XXZ(2) + hamiltonian = XXZ(2) # create VQE model for the circuit and Hamiltonian - vqe = models.VQE(circuit, hamiltonian) + vqe = VQE(circuit, hamiltonian) # optimize using random initial variational parameters initial_parameters = np.random.uniform(0, 2, 1) vqe.minimize(initial_parameters) @@ -154,20 +158,30 @@ class AAVQE: .. testcode:: import numpy as np - from qibo import gates, models, hamiltonians + + from qibo import Circuit, gates + from qibo.hamiltonians import X, XXZ + from qibo.models import AAVQE + # create circuit ansatz for two qubits - circuit = models.Circuit(2) + circuit = Circuit(2) circuit.add(gates.RY(0, theta=0)) circuit.add(gates.RY(1, theta=0)) # define the easy and the problem Hamiltonians. - easy_hamiltonian=hamiltonians.X(2) - problem_hamiltonian=hamiltonians.XXZ(2) + easy_hamiltonian = X(2) + problem_hamiltonian = XXZ(2) # define a scheduling function with only one parameter # and boundary conditions s(0) = 0, s(1) = 1 s = lambda t: t # create AAVQE model - aavqe = models.AAVQE(circuit, easy_hamiltonian, problem_hamiltonian, - s, nsteps=10, t_max=1) + aavqe = AAVQE( + circuit, + easy_hamiltonian, + problem_hamiltonian, + s, + nsteps=10, + t_max=1 + ) # optimize using random initial variational parameters np.random.seed(0) initial_parameters = np.random.uniform(0, 2*np.pi, 2) diff --git a/src/qibo/noise_model.py b/src/qibo/noise_model.py index 3dee0432ca..d4da3b6121 100644 --- a/src/qibo/noise_model.py +++ b/src/qibo/noise_model.py @@ -1,6 +1,6 @@ import numpy as np -from qibo import gates, models +from qibo import Circuit, gates from qibo.quantum_info.utils import hellinger_fidelity, hellinger_shot_error @@ -13,8 +13,8 @@ def noisy_circuit(circuit, params): Args: - circuit (qibo.models.Circuit): Circuit on which noise will be applied. Since in the end are - applied bitflips, measurement gates are required. + circuit (:class:`qibo.models.Circuit`): circuit on which noise will be applied. Since in the end are + applied bitflips, measurement gates are required. params (dict): contains the parameters of the channels organized as follow \n {'t1' : (``t1``, ``t2``,..., ``tn``), 't2' : (``t1``, ``t2``,..., ``tn``), @@ -30,10 +30,9 @@ def noisy_circuit(circuit, params): The fifth parameter is a tuple containing the depolaraziong errors for single and 2 qubit gate. The sisxth parameter is a tuple containg the two arrays for bitflips probability errors: the first one implements 0->1 errors, the other one 1->0. The last parameter is a boolean variable: if True the noise model takes into account idle qubits. - Returns: - The new noisy circuit (qibo.models.Circuit). - + Returns: + :class:`qibo.models.Circuit`: New noisy circuit. """ # parameters of the model t1 = params["t1"] @@ -48,7 +47,7 @@ def noisy_circuit(circuit, params): idle_qubits = params["idle_qubits"] # new circuit - noisy_circ = models.Circuit(circuit.nqubits, density_matrix=True) + noisy_circ = Circuit(circuit.nqubits, density_matrix=True) # time steps of the circuit time_steps = max(circuit.queue.moment_index) @@ -278,7 +277,7 @@ def __init__(self, params): def apply(self, circuit): """Creates the noisy circuit from the circuit given as argument by using the :func:`qibo.noise_model.noisy_circuit`. Args: - circuit (qibo.models.Circuit): the circuit you want to simulate. + circuit (:class:`qibo.models.Circuit`): Circuit to simulate. """ self.noisy_circuit = noisy_circuit(circuit, self.params) diff --git a/src/qibo/optimizers.py b/src/qibo/optimizers.py index dbd0331deb..0914800f34 100644 --- a/src/qibo/optimizers.py +++ b/src/qibo/optimizers.py @@ -58,7 +58,7 @@ def optimize( .. testcode:: import numpy as np - from qibo import gates, models + from qibo import Circuit, gates from qibo.optimizers import optimize # create custom loss function @@ -68,7 +68,7 @@ def myloss(parameters, circuit): return np.square(np.sum(circuit().state())) # returns numpy array # create circuit ansatz for two qubits - circuit = models.Circuit(2) + circuit = Circuit(2) circuit.add(gates.RY(0, theta=0)) # optimize using random initial variational parameters diff --git a/src/qibo/parallel.py b/src/qibo/parallel.py index 22a65fafe7..3dd03f0c13 100644 --- a/src/qibo/parallel.py +++ b/src/qibo/parallel.py @@ -2,7 +2,7 @@ Resources for parallel circuit evaluation. """ -from typing import Iterable +from typing import Iterable, Optional from joblib import Parallel, delayed @@ -10,7 +10,7 @@ from qibo.config import raise_error -def parallel_execution(circuit, states, processes=None, backend=None): +def parallel_execution(circuit, states, processes: Optional[int] = None, backend=None): """Execute circuit for multiple states. Example: @@ -32,9 +32,10 @@ def parallel_execution(circuit, states, processes=None, backend=None): results = parallel_execution(circuit, states, processes=2) Args: - circuit (qibo.models.Circuit): the input circuit. + circuit (:class:`qibo.models.Circuit`): the input circuit. states (list): list of states for the circuit evaluation. - processes (int): number of processes for parallel evaluation. + processes (int, optional): number of processes for parallel evaluation. + If ``None``, defaults to :math:`1`. Defaults to ``None``. Returns: Circuit evaluation for input states. @@ -119,22 +120,28 @@ def operation(circuit, state): def parallel_parametrized_execution( - circuit, parameters, initial_state=None, processes=None, backend=None + circuit, + parameters, + initial_state=None, + processes: Optional[int] = None, + backend=None, ): """Execute circuit for multiple parameters and fixed initial_state. Example: .. code-block:: python - import qibo - qibo.set_backend('qibojit') - from qibo import models, gates, set_threads - from qibo.parallel import parallel_parametrized_execution import numpy as np + + from qibo import Circuit, gates, set_backend, set_threads + from qibo.parallel import parallel_parametrized_execution + + set_backend('qibojit') + # create circuit nqubits = 6 nlayers = 2 - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(nlayers): circuit.add((gates.RY(q, theta=0) for q in range(nqubits))) circuit.add((gates.CZ(q, q+1) for q in range(0, nqubits-1, 2))) @@ -151,9 +158,9 @@ def parallel_parametrized_execution( results = parallel_parametrized_execution(circuit, parameters, processes=2) Args: - circuit (qibo.models.Circuit): the input circuit. + circuit (:class:`qibo.models.Circuit`): the input circuit. parameters (list): list of parameters for the circuit evaluation. - initial_state (np.array): initial state for the circuit evaluation. + initial_state (ndarray): initial state for the circuit evaluation. processes (int): number of processes for parallel evaluation. This corresponds to the number of threads, if a single thread is used for each circuit evaluation. If more threads are used for each circuit diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 439045e72a..c838262231 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -35,7 +35,7 @@ def __call__( """Match circuit to hardware connectivity. Args: - circuit (qibo.models.Circuit): circuit to be routed. + circuit (:class:`qibo.models.Circuit`): circuit to be routed. initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. Returns: diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index e9a95d713d..3949271085 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -227,7 +227,7 @@ def _initial_block_decomposition(circuit: Circuit): This decomposition is not minimal. Args: - circuit (qibo.models.Circuit): circuit to be decomposed. + circuit (:class:`qibo.models.Circuit`): circuit to be decomposed. Return: blocks (list): list of blocks that act on two qubits. diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index a6bf3a7b9d..c033850557 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -107,13 +107,16 @@ def assert_transpiling( """Check that all transpiler passes have been executed correctly. Args: - original_circuit (qibo.models.Circuit): circuit before transpiling. - transpiled_circuit (qibo.models.Circuit): circuit after transpiling. - connectivity (networkx.Graph): chip qubits connectivity. + original_circuit (:class:`qibo.models.Circuit`): circuit before transpiling. + transpiled_circuit (:class:`qibo.models.Circuit`): circuit after transpiling. + connectivity (:class:`networkx.Graph`): chip qubits connectivity. initial_layout (dict): initial physical-logical qubit mapping. final_layout (dict): final physical-logical qubit mapping. - native_gates (NativeGates): native gates supported by the hardware. - check_circuit_equivalence (Bool): use simulations to check if the transpiled circuit is the same as the original. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates + supported by the hardware. Defaults to + :meth:`qibo.transpiler.unroller.NativeGates.default`. + check_circuit_equivalence (bool, optional): use simulations to check if the + transpiled circuit is the same as the original. Defaults to ``True``. """ assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) assert_decomposition( diff --git a/tests/test_backends_global.py b/tests/test_backends_global.py index 45f990eb76..99c1f9d450 100644 --- a/tests/test_backends_global.py +++ b/tests/test_backends_global.py @@ -2,7 +2,7 @@ import pytest import qibo -from qibo import matrices +from qibo import Circuit, matrices from qibo.backends import _Global, get_backend from qibo.backends.numpy import NumpyBackend from qibo.transpiler.optimizer import Preprocessing @@ -86,10 +86,10 @@ def test_set_metropolis_threshold(): def test_circuit_execution(): qibo.set_backend("numpy") - c = qibo.models.Circuit(2) - c.add(qibo.gates.H(0)) - c() - c.unitary() + circuit = Circuit(2) + circuit.add(qibo.gates.H(0)) + circuit() + circuit.unitary() def test_gate_matrix(): diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py index 2549e6bd45..9dcd0f032a 100644 --- a/tests/test_backends_torch_gradients.py +++ b/tests/test_backends_torch_gradients.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from qibo import gates, models +from qibo import Circuit, gates from qibo.backends import PyTorchBackend, construct_backend from qibo.quantum_info import infidelity @@ -16,21 +16,21 @@ def test_torch_gradients(): target_state = backend.np.rand(4, dtype=backend.np.complex128) target_state = target_state / backend.np.norm(target_state) params = backend.np.rand(4, dtype=backend.np.float64, requires_grad=True) - c = models.Circuit(2) - c.add(gates.RX(0, params[0])) - c.add(gates.RY(1, params[1])) - c.add(gates.U2(1, params[2], params[3])) + circuit = Circuit(2) + circuit.add(gates.RX(0, params[0])) + circuit.add(gates.RY(1, params[1])) + circuit.add(gates.U2(1, params[2], params[3])) initial_params = params.clone() initial_loss = infidelity( - target_state, backend.execute_circuit(c).state(), backend=backend + target_state, backend.execute_circuit(circuit).state(), backend=backend ) optimizer = optimizer([params], lr=0.01) for _ in range(nepochs): optimizer.zero_grad() - c.set_parameters(params) - final_state = backend.execute_circuit(c).state() + circuit.set_parameters(params) + final_state = backend.execute_circuit(circuit).state() loss = infidelity(target_state, final_state, backend=backend) loss.backward() grad = params.grad.clone().norm() @@ -51,13 +51,13 @@ def test_torch_tensorflow_gradients(): target_state = backend.np.tensor([0.0, 1.0], dtype=backend.np.complex128) param = backend.np.tensor([0.1], dtype=backend.np.float64, requires_grad=True) - c = models.Circuit(1) - c.add(gates.RX(0, param[0])) + circuit = Circuit(1) + circuit.add(gates.RX(0, param[0])) optimizer = backend.np.optim.SGD optimizer = optimizer([param], lr=1) - c.set_parameters(param) - final_state = backend.execute_circuit(c).state() + circuit.set_parameters(param) + final_state = backend.execute_circuit(circuit).state() loss = infidelity(target_state, final_state, backend=backend) loss.backward() torch_param_grad = param.grad.clone().item() @@ -66,14 +66,14 @@ def test_torch_tensorflow_gradients(): target_state = tf_backend.tf.constant([0.0, 1.0], dtype=tf_backend.tf.complex128) param = tf_backend.tf.Variable([0.1], dtype=tf_backend.tf.float64) - c = models.Circuit(1) - c.add(gates.RX(0, param[0])) + circuit = Circuit(1) + circuit.add(gates.RX(0, param[0])) optimizer = tf_backend.tf.optimizers.SGD(learning_rate=1.0) with tf_backend.tf.GradientTape() as tape: - c.set_parameters(param) - final_state = tf_backend.execute_circuit(c).state() + circuit.set_parameters(param) + final_state = tf_backend.execute_circuit(circuit).state() loss = infidelity(target_state, final_state, backend=tf_backend) grads = tape.gradient(loss, [param]) diff --git a/tests/test_hamiltonians_terms.py b/tests/test_hamiltonians_terms.py index 6d3dafdbad..0addb08adc 100644 --- a/tests/test_hamiltonians_terms.py +++ b/tests/test_hamiltonians_terms.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from qibo import gates, matrices, models +from qibo import Circuit, gates, matrices from qibo.hamiltonians import terms from qibo.quantum_info import random_density_matrix, random_statevector @@ -49,15 +49,17 @@ def test_hamiltonian_term_gates(backend): initial_state = random_statevector(2**nqubits, backend=backend) final_state = term(backend, backend.np.copy(initial_state), nqubits) - c = models.Circuit(nqubits) - c.add(gates.Unitary(matrix, 1, 2)) - target_state = backend.execute_circuit(c, backend.np.copy(initial_state)).state() + circuit = Circuit(nqubits) + circuit.add(gates.Unitary(matrix, 1, 2)) + target_state = backend.execute_circuit( + circuit, backend.np.copy(initial_state) + ).state() backend.assert_allclose(final_state, target_state) def test_hamiltonian_term_exponentiation(backend): """Test exp gate application of ``HamiltonianTerm``.""" - from scipy.linalg import expm + from scipy.linalg import expm # pylint: disable=C0415 matrix = np.random.random((2, 2)) term = terms.HamiltonianTerm(matrix, 1) diff --git a/tests/test_measurements_collapse.py b/tests/test_measurements_collapse.py index 2944a67783..ce38b039a9 100644 --- a/tests/test_measurements_collapse.py +++ b/tests/test_measurements_collapse.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from qibo import gates, models +from qibo import Circuit, gates from qibo.quantum_info import random_density_matrix, random_statevector @@ -13,7 +13,7 @@ ) def test_measurement_collapse(backend, nqubits, targets): initial_state = random_statevector(2**nqubits, backend=backend) - c = models.Circuit(nqubits) + c = Circuit(nqubits) for q in np.random.randint(nqubits, size=np.random.randint(nqubits, size=1)): c.add(gates.H(q)) r = c.add(gates.M(*targets, collapse=True)) @@ -39,7 +39,7 @@ def assign_value(rho, index, value): return rho initial_rho = random_density_matrix(2**nqubits, backend=backend) - c = models.Circuit(nqubits, density_matrix=True) + c = Circuit(nqubits, density_matrix=True) r = c.add(gates.M(*targets, collapse=True)) final_rho = backend.execute_circuit(c, backend.np.copy(initial_rho), nshots=1) @@ -60,7 +60,7 @@ def assign_value(rho, index, value): def test_measurement_collapse_bitflip_noise(backend): - c = models.Circuit(4) + c = Circuit(4) with pytest.raises(NotImplementedError): output = c.add(gates.M(0, 1, p0=0.2, collapse=True)) @@ -68,7 +68,7 @@ def test_measurement_collapse_bitflip_noise(backend): @pytest.mark.parametrize("density_matrix", [True, False]) @pytest.mark.parametrize("effect", [False, True]) def test_measurement_result_parameters(backend, effect, density_matrix): - c = models.Circuit(4, density_matrix=density_matrix) + c = Circuit(4, density_matrix=density_matrix) if effect: c.add(gates.X(0)) r = c.add(gates.M(0, collapse=True)) @@ -76,7 +76,7 @@ def test_measurement_result_parameters(backend, effect, density_matrix): if not density_matrix: c.add(gates.M(0)) - target_c = models.Circuit(4, density_matrix=density_matrix) + target_c = Circuit(4, density_matrix=density_matrix) if effect: target_c.add(gates.X(0)) target_c.add(gates.RX(1, theta=np.pi / 4)) @@ -94,7 +94,7 @@ def test_measurement_result_parameters(backend, effect, density_matrix): def test_measurement_result_parameters_random(backend): initial_state = random_density_matrix(2**4, backend=backend) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) r = c.add(gates.M(1, collapse=True)) c.add(gates.RY(0, theta=np.pi * r.symbols[0] / 5)) c.add(gates.RX(2, theta=np.pi * r.symbols[0] / 4)) @@ -103,13 +103,13 @@ def test_measurement_result_parameters_random(backend): ) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) m = c.add(gates.M(1, collapse=True)) target_state = backend.execute_circuit( c, initial_state=backend.np.copy(initial_state), nshots=1 ).state() if int(m.symbols[0].outcome()): - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) c.add(gates.RY(0, theta=np.pi / 5)) c.add(gates.RX(2, theta=np.pi / 4)) target_state = backend.execute_circuit(c, initial_state=target_state) @@ -120,7 +120,7 @@ def test_measurement_result_parameters_random(backend): def test_measurement_result_parameters_repeated_execution(backend, use_loop): initial_state = random_density_matrix(2**4, backend=backend) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) r = c.add(gates.M(1, collapse=True)) c.add(gates.RX(2, theta=np.pi * r.symbols[0] / 4)) if use_loop: @@ -139,7 +139,7 @@ def test_measurement_result_parameters_repeated_execution(backend, use_loop): backend.set_seed(123) target_states = [] for _ in range(20): - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) m = c.add(gates.M(1, collapse=True)) target_state = backend.execute_circuit( c, backend.np.copy(initial_state), nshots=1 @@ -157,7 +157,7 @@ def test_measurement_result_parameters_repeated_execution(backend, use_loop): def test_measurement_result_parameters_repeated_execution_final_measurements(backend): initial_state = random_density_matrix(2**4, backend=backend) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) r = c.add(gates.M(1, collapse=True)) c.add(gates.RY(0, theta=np.pi * r.symbols[0] / 3)) c.add(gates.RY(2, theta=np.pi * r.symbols[0] / 4)) @@ -170,12 +170,12 @@ def test_measurement_result_parameters_repeated_execution_final_measurements(bac backend.set_seed(123) target_samples = [] for _ in range(30): - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) m = c.add(gates.M(1, collapse=True)) target_state = backend.execute_circuit( c, backend.np.copy(initial_state), nshots=1 ).state() - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) if int(m.symbols[0].outcome()): c.add(gates.RY(0, theta=np.pi / 3)) c.add(gates.RY(2, theta=np.pi / 4)) @@ -188,14 +188,14 @@ def test_measurement_result_parameters_repeated_execution_final_measurements(bac def test_measurement_result_parameters_multiple_qubits(backend): initial_state = random_density_matrix(2**4, backend=backend) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) r = c.add(gates.M(0, 1, 2, collapse=True)) c.add(gates.RY(1, theta=np.pi * r.symbols[0] / 5)) c.add(gates.RX(3, theta=np.pi * r.symbols[2] / 3)) final_state = backend.execute_circuit(c, backend.np.copy(initial_state), nshots=1) backend.set_seed(123) - c = models.Circuit(4, density_matrix=True) + c = Circuit(4, density_matrix=True) m = c.add(gates.M(0, 1, 2, collapse=True)) target_state = backend.execute_circuit( c, backend.np.copy(initial_state), nshots=1 @@ -217,7 +217,7 @@ def test_measurement_result_parameters_multiple_qubits(backend): @pytest.mark.parametrize("nqubits,targets", [(5, [2, 4]), (6, [3, 5])]) def test_measurement_collapse_distributed(backend, accelerators, nqubits, targets): initial_state = random_density_matrix(2**nqubits, backend=backend) - c = models.Circuit(nqubits, accelerators, density_matrix=True) + c = Circuit(nqubits, accelerators, density_matrix=True) m = c.add(gates.M(*targets, collapse=True)) result = backend.execute_circuit(c, np.copy(initial_state), nshots=1).state() slicer = 2 * nqubits * [slice(None)] @@ -235,13 +235,13 @@ def test_measurement_collapse_distributed(backend, accelerators, nqubits, target def test_collapse_after_measurement(backend): qubits = [0, 2, 3] - c = models.Circuit(5, density_matrix=True) + c = Circuit(5, density_matrix=True) c.add(gates.H(i) for i in range(5)) m = c.add(gates.M(*qubits, collapse=True)) c.add(gates.H(i) for i in range(5)) final_state = backend.execute_circuit(c, nshots=1) - ct = models.Circuit(5, density_matrix=True) + ct = Circuit(5, density_matrix=True) bitstring = [r.outcome() for r in m.symbols] for i, r in zip(qubits, bitstring): if r: @@ -252,7 +252,7 @@ def test_collapse_after_measurement(backend): def test_collapse_error(backend): - c = models.Circuit(1) + c = Circuit(1) m = c.add(gates.M(0, collapse=True)) with pytest.raises(Exception) as exc_info: backend.execute_circuit(c) diff --git a/tests/test_measurements_probabilistic.py b/tests/test_measurements_probabilistic.py index 742c4838b2..a831e23ba8 100644 --- a/tests/test_measurements_probabilistic.py +++ b/tests/test_measurements_probabilistic.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from qibo import gates, models +from qibo import Circuit, gates from .test_measurements import assert_result @@ -12,11 +12,11 @@ def test_probabilistic_measurement(backend, accelerators, use_samples): # set single-thread to fix the random values generated from the frequency custom op backend.set_threads(1) - c = models.Circuit(4, accelerators) - c.add(gates.H(0)) - c.add(gates.H(1)) - c.add(gates.M(0, 1)) - result = backend.execute_circuit(c, nshots=1000) + circuit = Circuit(4, accelerators) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + circuit.add(gates.M(0, 1)) + result = backend.execute_circuit(circuit, nshots=1000) backend.set_seed(1234) if use_samples: @@ -33,11 +33,11 @@ def test_probabilistic_measurement(backend, accelerators, use_samples): def test_sample_frequency_agreement(backend): # set single-thread to fix the random values generated from the frequency custom op backend.set_threads(1) - c = models.Circuit(2) - c.add(gates.H(0)) - c.add(gates.H(1)) - c.add(gates.M(0, 1)) - result = backend.execute_circuit(c, nshots=1000) + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.H(1)) + circuit.add(gates.M(0, 1)) + result = backend.execute_circuit(circuit, nshots=1000) backend.set_seed(1234) target_frequencies = result.frequencies(binary=False) @@ -54,9 +54,9 @@ def test_unbalanced_probabilistic_measurement(backend, use_samples): # set single-thread to fix the random values generated from the frequency custom op backend.set_threads(1) state = np.array([1, 1, 1, np.sqrt(3)]) / np.sqrt(6) - c = models.Circuit(2) - c.add(gates.M(0, 1)) - result = backend.execute_circuit(c, initial_state=np.copy(state), nshots=1000) + circuit = Circuit(2) + circuit.add(gates.M(0, 1)) + result = backend.execute_circuit(circuit, initial_state=np.copy(state), nshots=1000) backend.set_seed(1234) if use_samples: @@ -75,14 +75,14 @@ def test_unbalanced_probabilistic_measurement(backend, use_samples): def test_measurements_with_probabilistic_noise(backend): """Check measurements when simulating noise with repeated execution.""" thetas = np.random.random(5) - c = models.Circuit(5) - c.add((gates.RX(i, t) for i, t in enumerate(thetas))) - c.add( + circuit = Circuit(5) + circuit.add((gates.RX(i, t) for i, t in enumerate(thetas))) + circuit.add( gates.PauliNoiseChannel(i, list(zip(["Y", "Z"], [0.2, 0.4]))) for i in range(5) ) - c.add(gates.M(*range(5))) + circuit.add(gates.M(*range(5))) backend.set_seed(123) - result = backend.execute_circuit(c, nshots=20) + result = backend.execute_circuit(circuit, nshots=20) samples = result.samples() backend.set_seed(123) @@ -90,14 +90,14 @@ def test_measurements_with_probabilistic_noise(backend): channel_gates = [gates.Y, gates.Z] probs = [0.2, 0.4, 0.4] for _ in range(20): - noiseless_c = models.Circuit(5) - noiseless_c.add((gates.RX(i, t) for i, t in enumerate(thetas))) + noiseless_circuit = Circuit(5) + noiseless_circuit.add((gates.RX(i, t) for i, t in enumerate(thetas))) for i in range(5): index = backend.sample_shots(probs, 1)[0] if index != len(channel_gates): - noiseless_c.add(channel_gates[index](i)) - noiseless_c.add(gates.M(*range(5))) - result = backend.execute_circuit(noiseless_c, nshots=1) + noiseless_circuit.add(channel_gates[index](i)) + noiseless_circuit.add(gates.M(*range(5))) + result = backend.execute_circuit(noiseless_circuit, nshots=1) target_samples.append(backend.to_numpy(result.samples())) target_samples = np.concatenate(target_samples, axis=0) backend.assert_allclose(samples, target_samples) @@ -109,11 +109,11 @@ def test_measurements_with_probabilistic_noise(backend): def test_post_measurement_bitflips_on_circuit(backend, accelerators, i, probs): """Check bitflip errors on circuit measurements.""" backend.set_seed(123) - c = models.Circuit(5, accelerators=accelerators) - c.add([gates.X(0), gates.X(2), gates.X(3)]) - c.add(gates.M(0, 1, p0={0: probs[0], 1: probs[1]})) - c.add(gates.M(3, p0=probs[2])) - result = backend.execute_circuit(c, nshots=30) + circuit = Circuit(5, accelerators=accelerators) + circuit.add([gates.X(0), gates.X(2), gates.X(3)]) + circuit.add(gates.M(0, 1, p0={0: probs[0], 1: probs[1]})) + circuit.add(gates.M(3, p0=probs[2])) + result = backend.execute_circuit(circuit, nshots=30) freqs = result.frequencies(binary=False) targets = backend._test_regressions("test_post_measurement_bitflips_on_circuit") assert freqs == targets[i] @@ -123,11 +123,11 @@ def test_post_measurement_bitflips_on_circuit_result(backend): """Check bitflip errors on ``CircuitResult`` objects.""" thetas = np.random.random(4) backend.set_seed(123) - c = models.Circuit(4) - c.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) - c.add(gates.M(0, 1, register_name="a", p0={0: 0.2, 1: 0.4})) - c.add(gates.M(3, register_name="b", p0=0.3)) - result = backend.execute_circuit(c, nshots=30) + circuit = Circuit(4) + circuit.add((gates.RX(i, theta=t) for i, t in enumerate(thetas))) + circuit.add(gates.M(0, 1, register_name="a", p0={0: 0.2, 1: 0.4})) + circuit.add(gates.M(3, register_name="b", p0=0.3)) + result = backend.execute_circuit(circuit, nshots=30) samples = result.samples(binary=True) register_samples = result.samples(binary=True, registers=True) backend.assert_allclose(register_samples["a"], samples[:, :2]) @@ -146,10 +146,10 @@ def test_post_measurement_bitflips_on_circuit_result(backend): def test_measurementresult_apply_bitflips(backend, i, p0, p1): from qibo.result import CircuitResult - c = models.Circuit(3) - c.add(gates.M(*range(3))) + circuit = Circuit(3) + circuit.add(gates.M(*range(3))) state = backend.zero_state(8) - result = CircuitResult(state, c.measurements, backend) + result = CircuitResult(state, circuit.measurements, backend) result._samples = backend.cast(np.zeros((10, 3)), dtype="int32") backend.set_seed(123) noisy_samples = result.apply_bitflips(p0, p1) diff --git a/tests/test_models_variational.py b/tests/test_models_variational.py index 8704aaa31f..1c49f4bacf 100644 --- a/tests/test_models_variational.py +++ b/tests/test_models_variational.py @@ -8,9 +8,11 @@ import pytest from scipy.linalg import expm -from qibo import gates, hamiltonians, models +from qibo import Circuit, gates +from qibo.hamiltonians import TFIM, XXZ, X, Y from qibo.models.utils import cvar, gibbs -from qibo.quantum_info import random_statevector +from qibo.models.variational import AAVQE, FALQON, QAOA, VQE +from qibo.quantum_info.random_ensembles import random_statevector REGRESSION_FOLDER = pathlib.Path(__file__).with_name("regressions") @@ -63,14 +65,14 @@ def myloss(parameters, circuit, target): nlayers = 4 # Create variational circuit - c = models.Circuit(nqubits) + circuit = Circuit(nqubits) for _ in range(nlayers): - c.add(gates.RY(q, theta=0) for q in range(nqubits)) - c.add(gates.CZ(q, q + 1) for q in range(0, nqubits - 1, 2)) - c.add(gates.RY(q, theta=0) for q in range(nqubits)) - c.add(gates.CZ(q, q + 1) for q in range(1, nqubits - 2, 2)) - c.add(gates.CZ(0, nqubits - 1)) - c.add(gates.RY(q, theta=0) for q in range(nqubits)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(0, nqubits - 1, 2)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(1, nqubits - 2, 2)) + circuit.add(gates.CZ(0, nqubits - 1)) + circuit.add(gates.RY(qubit, theta=0) for qubit in range(nqubits)) # Optimize starting from a random guess for the variational parameters np.random.seed(0) @@ -78,8 +80,13 @@ def myloss(parameters, circuit, target): data = np.random.normal(0, 1, size=2**nqubits) # perform optimization - best, params, _ = optimize( - myloss, x0, args=(c, data), method=method, options=options, compile=compile + _, params, _ = optimize( + myloss, + x0, + args=(circuit, data), + method=method, + options=options, + compile=compile, ) if filename is not None: assert_regression_fixture(backend, params, filename) @@ -113,7 +120,7 @@ def test_vqe(backend, method, options, compile, filename): backend.set_threads(1) nqubits = 3 layers = 4 - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) @@ -126,14 +133,14 @@ def test_vqe(backend, method, options, compile, filename): circuit.add(gates.CZ(0, nqubits - 1)) for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) - hamiltonian = hamiltonians.XXZ(nqubits=nqubits, backend=backend) + hamiltonian = XXZ(nqubits=nqubits, backend=backend) np.random.seed(0) initial_parameters = backend.cast( np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits), dtype="float64" ) if backend.name == "pytorch": initial_parameters.requires_grad = True - v = models.VQE(circuit, hamiltonian) + v = VQE(circuit, hamiltonian) loss_values = [] @@ -176,8 +183,8 @@ def callback(parameters, loss_values=loss_values, vqe=v): ], ) def test_qaoa_execution(backend, solver, dense, accel=None): - h = hamiltonians.TFIM(6, h=1.0, dense=dense, backend=backend) - m = hamiltonians.X(6, dense=dense, backend=backend) + h = TFIM(6, h=1.0, dense=dense, backend=backend) + m = X(6, dense=dense, backend=backend) # Trotter and RK require small p's! params = 0.01 * (1 - 2 * np.random.random(4)) state = random_statevector(2**6, backend=backend) @@ -199,7 +206,7 @@ def test_qaoa_execution(backend, solver, dense, accel=None): u = expm(-1j * p * h_matrix) target_state = backend.cast(u) @ target_state - qaoa = models.QAOA(h, mixer=m, solver=solver, accelerators=accel) + qaoa = QAOA(h, mixer=m, solver=solver, accelerators=accel) qaoa.set_parameters(params) final_state = qaoa(backend.cast(state, copy=True)) backend.assert_allclose(final_state, target_state, atol=atol) @@ -214,13 +221,13 @@ def test_qaoa_callbacks(backend, accelerators): # use ``Y`` Hamiltonian so that there are no errors # in the Trotter decomposition - h = hamiltonians.Y(5, backend=backend) + h = Y(5, backend=backend) energy = callbacks.Energy(h) params = 0.1 * np.random.random(4) state = random_statevector(2**5, backend=backend) - ham = hamiltonians.Y(5, dense=False, backend=backend) - qaoa = models.QAOA(ham, callbacks=[energy], accelerators=accelerators) + ham = Y(5, dense=False, backend=backend) + qaoa = QAOA(ham, callbacks=[energy], accelerators=accelerators) qaoa.set_parameters(params) final_state = qaoa(backend.cast(state, copy=True)) @@ -243,22 +250,22 @@ def test_qaoa_callbacks(backend, accelerators): def test_qaoa_errors(backend): # Invalid Hamiltonian type with pytest.raises(TypeError): - qaoa = models.QAOA("test") + qaoa = QAOA("test") # Hamiltonians of different type - h = hamiltonians.TFIM(4, h=1.0, dense=False, backend=backend) - m = hamiltonians.X(4, dense=True, backend=backend) + h = TFIM(4, h=1.0, dense=False, backend=backend) + m = X(4, dense=True, backend=backend) with pytest.raises(TypeError): - qaoa = models.QAOA(h, mixer=m) + qaoa = QAOA(h, mixer=m) # Hamiltonians acting on different qubit numbers - h = hamiltonians.TFIM(6, h=1.0, backend=backend) - m = hamiltonians.X(4, backend=backend) + h = TFIM(6, h=1.0, backend=backend) + m = X(4, backend=backend) with pytest.raises(ValueError): - qaoa = models.QAOA(h, mixer=m) + qaoa = QAOA(h, mixer=m) # distributed execution with RK solver with pytest.raises(NotImplementedError): - qaoa = models.QAOA(h, solver="rk4", accelerators={"/GPU:0": 2}) + qaoa = QAOA(h, solver="rk4", accelerators={"/GPU:0": 2}) # minimize with odd number of parameters - qaoa = models.QAOA(h) + qaoa = QAOA(h) with pytest.raises(ValueError): qaoa.minimize(np.random.random(5)) @@ -278,8 +285,8 @@ def test_qaoa_optimization(backend, method, options, dense, filename): pytest.skip("Skipping SGD test for unsupported backend.") if method != "sgd" and backend.name in ("tensorflow", "pytorch"): pytest.skip("Skipping scipy optimizers for tensorflow and pytorch.") - h = hamiltonians.XXZ(3, dense=dense, backend=backend) - qaoa = models.QAOA(h) + h = XXZ(3, dense=dense, backend=backend) + qaoa = QAOA(h) initial_p = backend.cast([0.05, 0.06, 0.07, 0.08], dtype="float64") if backend.name == "pytorch": initial_p.requires_grad = True @@ -299,8 +306,8 @@ def test_qaoa_optimization(backend, method, options, dense, filename): @pytest.mark.parametrize(test_names, test_values) def test_falqon_optimization(backend, delta_t, max_layers, tolerance, filename): - h = hamiltonians.XXZ(3, backend=backend) - falqon = models.FALQON(h) + h = XXZ(3, backend=backend) + falqon = FALQON(h) best, params, extra = falqon.minimize(delta_t, max_layers, tol=tolerance) if filename is not None: assert_regression_fixture(backend, params, filename) @@ -312,8 +319,8 @@ def __call__(self, x): return np.sum(x) callback = TestCallback() - h = hamiltonians.XXZ(3, backend=backend) - falqon = models.FALQON(h) + h = XXZ(3, backend=backend) + falqon = FALQON(h) best, params, extra = falqon.minimize(0.1, 5, callback=callback) assert len(extra["callbacks"]) == 5 @@ -332,7 +339,7 @@ def test_aavqe(backend, method, options, compile, filename): nqubits = 4 layers = 1 - circuit = models.Circuit(nqubits) + circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): @@ -347,12 +354,10 @@ def test_aavqe(backend, method, options, compile, filename): for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) - easy_hamiltonian = hamiltonians.X(nqubits, backend=backend) - problem_hamiltonian = hamiltonians.XXZ(nqubits, backend=backend) + easy_hamiltonian = X(nqubits, backend=backend) + problem_hamiltonian = XXZ(nqubits, backend=backend) s = lambda t: t - aavqe = models.AAVQE( - circuit, easy_hamiltonian, problem_hamiltonian, s, nsteps=10, t_max=1 - ) + aavqe = AAVQE(circuit, easy_hamiltonian, problem_hamiltonian, s, nsteps=10, t_max=1) np.random.seed(0) initial_parameters = np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits) best, params = aavqe.minimize( @@ -374,8 +379,8 @@ def test_aavqe(backend, method, options, compile, filename): def test_custom_loss(test_input, test_param, expected): from qibo import hamiltonians - h = hamiltonians.XXZ(3) - qaoa = models.QAOA(h) + h = XXZ(3) + qaoa = QAOA(h) initial_p = [0.314, 0.22, 0.05, 0.59] best, params, _ = qaoa.minimize( initial_p, loss_func=test_input, loss_func_param=test_param diff --git a/tests/test_result.py b/tests/test_result.py index 7b37466a2f..3d57f0140a 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -3,26 +3,25 @@ import numpy as np import pytest -from qibo import Circuit, gates, models -from qibo.config import raise_error +from qibo import Circuit, gates from qibo.result import CircuitResult, MeasurementOutcomes, load_result @pytest.mark.parametrize("qubits", [None, [0], [1, 2]]) def test_measurementoutcomes_probabilities(backend, qubits): - c = Circuit(3) - c.add(gates.H(0)) - c.add(gates.M(0, 2)) - global_probs = backend.execute_circuit(c).probabilities(qubits=[0, 2]) + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.M(0, 2)) + global_probs = backend.execute_circuit(circuit).probabilities(qubits=[0, 2]) probabilities = ( - backend.execute_circuit(c).probabilities(qubits=qubits) + backend.execute_circuit(circuit).probabilities(qubits=qubits) if qubits is not None - else backend.execute_circuit(c).probabilities(qubits=[0, 2]) + else backend.execute_circuit(circuit).probabilities(qubits=[0, 2]) ) - c.has_collapse = True + circuit.has_collapse = True if qubits is not None and 1 in qubits: with pytest.raises(RuntimeError) as excinfo: - repeated_probabilities = backend.execute_circuit(c).probabilities( + repeated_probabilities = backend.execute_circuit(circuit).probabilities( qubits=qubits ) assert ( @@ -30,28 +29,28 @@ def test_measurementoutcomes_probabilities(backend, qubits): == f"Asking probabilities for qubits {qubits}, but only qubits [0,2] were measured." ) else: - repeated_probabilities = backend.execute_circuit(c, nshots=1000).probabilities( - qubits=qubits - ) + repeated_probabilities = backend.execute_circuit( + circuit, nshots=1000 + ).probabilities(qubits=qubits) result = MeasurementOutcomes( - c.measurements, backend=backend, probabilities=global_probs + circuit.measurements, backend=backend, probabilities=global_probs ) backend.assert_allclose(probabilities, repeated_probabilities, atol=1e-1) backend.assert_allclose(result.probabilities(qubits), probabilities, atol=1e-1) def test_measurementoutcomes_samples_from_measurements(backend): - c = Circuit(3) - c.add(gates.H(0)) - c.add(gates.M(0, 2)) - res = backend.execute_circuit(c) + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.M(0, 2)) + res = backend.execute_circuit(circuit) samples = res.samples() - outcome = MeasurementOutcomes(c.measurements, backend=backend) + outcome = MeasurementOutcomes(circuit.measurements, backend=backend) backend.assert_allclose(samples, outcome.samples()) def test_circuit_result_error(backend): - c = models.Circuit(1) + c = Circuit(1) state = np.array([1, 0]) with pytest.raises(Exception) as exc_info: CircuitResult(state, c.measurements, backend) @@ -62,7 +61,7 @@ def test_circuit_result_error(backend): def test_measurement_gate_dump_load(backend): - c = models.Circuit(2) + c = Circuit(2) c.add(gates.M(1, 0, basis=[gates.Z, gates.X])) m = c.measurements load = m[0].to_json() @@ -72,7 +71,7 @@ def test_measurement_gate_dump_load(backend): @pytest.mark.parametrize("agnostic_load", [False, True]) def test_measurementoutcomes_dump_load(backend, agnostic_load): - c = models.Circuit(2) + c = Circuit(2) c.add(gates.M(1, 0, basis=[gates.Z, gates.X])) # just to trigger repeated execution and test MeasurementOutcomes c.has_collapse = True @@ -91,7 +90,7 @@ def test_measurementoutcomes_dump_load(backend, agnostic_load): @pytest.mark.parametrize("agnostic_load", [False, True]) def test_circuitresult_dump_load(backend, agnostic_load): - c = models.Circuit(2, density_matrix=True) + c = Circuit(2, density_matrix=True) c.add(gates.M(1, 0, basis=[gates.Z, gates.X])) # trigger repeated execution to build the CircuitResult object # from samples and recover the same exact frequencies