qiboteam / qibo

A full-stack framework for quantum computing.
https://qibo.science
Apache License 2.0
297 stars 61 forks source link

Define a general agreement on what a `Backend` should be able to do #1474

Open BrunoLiegiBastonLiegi opened 1 month ago

BrunoLiegiBastonLiegi commented 1 month ago

Right now we support several backends, some inside qibo and others outside of it: qibojit, qibo-cloud, qiboml, qibolab ... However it is nowhere defined what exactly each backend should provide to be compatible with the rest of the qibo API, for instance the quantum_info module. At the moment we rely on the self.np or backend.np trick to trigger the "engine" of each separate backend. However this is neither consistent across the backends, PyTorchBackend has self.np = torch whereas TensorflowBackend has self.tf = tf and self.np = tf.experimental.numpy or CupyBackend has self.np = numpy and self.cp = cupy, nor is enough to cover all the needed cases. A quick search with git grep "if backend"shows several situations in which we need to explicitely check which backend is used and act accordingly:

git grep "if backend" ``` src/qibo/backends/__init__.py: if backend == "numpy": src/qibo/backends/__init__.py: elif backend == "tensorflow": src/qibo/backends/__init__.py: elif backend == "pytorch": src/qibo/backends/__init__.py: elif backend == "clifford": src/qibo/backends/__init__.py: elif backend == "qulacs": src/qibo/backends/__init__.py: if backend: # pragma: no cover src/qibo/backends/__init__.py: if backend is None: src/qibo/backends/__init__.py: if backend in QIBO_NATIVE_BACKENDS + ("clifford",): src/qibo/backends/__init__.py: if backend.__class__.__name__ in [ src/qibo/backends/clifford.py: return backend.platform if backend.platform is not None else backend.name src/qibo/models/error_mitigation.py: if backend.name == "pytorch": src/qibo/models/error_mitigation.py: if backend.name == "pytorch": src/qibo/models/error_mitigation.py: if backend.name == "pytorch": src/qibo/models/error_mitigation.py: if backend is None: # pragma: no cover src/qibo/models/error_mitigation.py: elif backend.name == "qibolab": # pragma: no cover src/qibo/optimizers.py: if backend.name == "tensorflow": src/qibo/optimizers.py: if backend.name == "pytorch": src/qibo/quantum_info/metrics.py: if backend.np.real(eig) > PRECISION_TOL: src/qibo/quantum_info/quantum_networks.py: if backend is None: # pragma: no cover src/qibo/quantum_info/quantum_networks.py: if backend is None: # pragma: no cover src/qibo/quantum_info/quantum_networks.py: if backend is None: # pragma: no cover src/qibo/quantum_info/random_ensembles.py: if backend.name == "tensorflow": src/qibo/quantum_info/superoperator_transformations.py: if backend.np.abs(eig) > precision_tol: src/qibo/quantum_info/utils.py: if backend.np.abs(backend.np.sum(prob_dist_p) - 1.0) > PRECISION_TOL: src/qibo/quantum_info/utils.py: if backend.np.abs(backend.np.sum(prob_dist_q) - 1.0) > PRECISION_TOL: src/qibo/quantum_info/utils.py: if backend.np.abs(backend.np.sum(prob_dist_p) - 1.0) > PRECISION_TOL: src/qibo/quantum_info/utils.py: if backend.np.abs(backend.np.sum(prob_dist_q) - 1.0) > PRECISION_TOL: src/qibo/tomography/gate_set_tomography.py: if backend.name == "qibolab" and transpiler is None: # pragma: no cover src/qibo/transpiler/unitary_decompositions.py: if backend.__class__.__name__ in [ src/qibo/transpiler/unitary_decompositions.py: if backend.__class__.__name__ != "PyTorchBackend": src/qibo/transpiler/unitary_decompositions.py: if backend.__class__.__name__ == "TensorflowBackend": src/qibo/transpiler/unitary_decompositions.py: if backend.__class__.__name__ == "TensorflowBackend": tests/test_backends_clifford.py: GlobalBackend().name if backend.name == "numpy" else GlobalBackend().platform tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow" and sparse_type is not None: tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_hamiltonians.py: if backend.name == "tensorflow": tests/test_hamiltonians.py: elif backend.name == "pytorch": tests/test_measurements_collapse.py: if backend.name == "tensorflow": tests/test_models_circuit_features.py: if backend.__class__.__name__ == "TensorflowBackend": tests/test_models_circuit_features.py: elif backend.__class__.__name__ == "PyTorchBackend": tests/test_models_error_mitigation.py: if backend.name == "tensorflow": tests/test_models_error_mitigation.py: if backend.name == "tensorflow": tests/test_models_error_mitigation.py: if backend.name == "tensorflow": tests/test_models_error_mitigation.py: if backend.name == "tensorflow": tests/test_models_error_mitigation.py: if backend.name == "tensorflow": tests/test_models_evolution.py: if backend.name != "tensorflow": tests/test_models_qft.py: if backend is not None: tests/test_models_variational.py: if backend.name == "pytorch": tests/test_models_variational.py: if backend.name == "pytorch": tests/test_quantum_info_clifford.py: if backend.__class__.__name__ == "TensorflowBackend": tests/test_quantum_info_clifford.py: elif backend.__class__.__name__ == "PyTorchBackend": tests/test_quantum_info_entropies.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_quantum_info_entropies.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_quantum_info_entropies.py: if backend.__class__.__name__ == "CupyBackend": tests/test_quantum_info_random.py: if backend.name == "pytorch" tests/test_transpiler_decompositions.py: if backend.np.allclose( tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/test_transpiler_unitary_decompositions.py: if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: tests/utils.py: if backend.name == "tensorflow": ```

which is not a sustainable strategy with the inclusion of more and more backends. Especially, an external backend provider should be able to know which operations the backend has to support to have a reasonable "guarantee" of being compatible with qibo.

I think, therefore, that we should compile a list of necessary functionalities, i.e. backend methods, that every backend has to expose to grant compatibility.

alecandido commented 1 month ago

https://github.com/qiboteam/qibo-core/issues/11

BrunoLiegiBastonLiegi commented 2 days ago

Just to complement on this, I would like to summarize here the list of methods that are called through the self.np.method or backend.np.method pattern and that, therefore, are candidate to be converted to explicit methods of the Backend.

git grep "self.np\." ``` self.np.eye self.np.zeros self.np.ones self.np.copy self.np.random.choice self.np.vstack self.np.cos self.np.sin self.np.array self.np.exp self.np.conj self.np.__version__ self.np.reshape self.np.transpose self.np.einsum self.np.concatenate self.np.expand_dims self.np.sqrt self.np.tensordot self.np.real self.np.sum self.np.abs self.np.unique self.np.random.seed self.np.linalg.norm self.np.trace self.np.matmul self.np.diag self.np.linalg.svd self.np.as_tensor self.np.stack self.np.device self.np.linalg.det self.np.permute self.np.Tensor self.np.clone self.np.pow self.np.mod self.np.remainder self.np.unsqueeze self.np.right_shift self.np.bitwise_right_shift self.np.sign self.np.sgn self.np.flatnonzero self.np.nonzero self.np.tensor self.np.manual_seed self.np.multinomial self.np.linalg.eigvalsh self.np.linalg.eigvals self.np.linalg.eigh self.np.linalg.eig self.np.linalg.matrix_exp self.np.imag self.np.autograd.functional.jacobian self.np.finfo self.np.identity ```
git grep "backend.np\." ``` backend.np.random.default_rng backend.np.less backend.np.outer backend.np.count_nonzero backend.np.flip backend.np.diagonal backend.np.mean backend.np.optim.LBFGS backend.np.any backend.np.std backend.np.float64 backend.np.where backend.np.log2 backend.np.tensordot backend.np.absolute backend.np.arccos backend.np.linalg.qr backend.np.kron backend.np.squeeze backend.np.swapaxes backend.np.arctan2 backend.np.angle backend.np.multiply backend.np.allclose backend.np.prod backend.np.optim.Adam backend.np.rand backend.np.optim.SGD backend.np.norm backend.np.complex128 backend.np.int64 backend.np.sort backend.np.log backend.np.inverse ```