Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New backend specialised in Hamming-weight-preserving circuits #1565

Open
wants to merge 100 commits into
base: master
Choose a base branch
from

Conversation

renatomello
Copy link
Contributor

@renatomello renatomello commented Jan 25, 2025

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.

@renatomello renatomello self-assigned this Jan 25, 2025
@renatomello renatomello added this to the Qibo 0.2.16 milestone Jan 25, 2025
@renatomello renatomello added documentation Improvements or additions to documentation enhancement New feature or request labels Jan 25, 2025
@renatomello renatomello changed the base branch from master to hwk_encoder January 25, 2025 06:02
@renatomello
Copy link
Contributor Author

@BrunoLiegiBastonLiegi could you help me out with setting the correct engine from the name of the platform? I didn't understand what you did for the Clifford backend.

@renatomello renatomello changed the title New Hamming-weight-preserving backend New backend specialised for Hamming-weight-preserving circuits Jan 25, 2025
@renatomello renatomello deleted the branch master January 29, 2025 14:02
@renatomello
Copy link
Contributor Author

This was closed by mistake, so I'm reopening it.

@renatomello renatomello reopened this Jan 29, 2025
@renatomello
Copy link
Contributor Author

@BrunoLiegiBastonLiegi @stavros11 sans a handful of docstrings that @AlejandroSopena will update today, this is ready for review.

Copy link
Contributor

@BrunoLiegiBastonLiegi BrunoLiegiBastonLiegi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I started looking into this, but before going ahead with the review I wished to discuss about the possibility of changing the structure of the backend, as I detail in one of the comments below.

Comment on lines +2714 to +2715
states of Hamming weight :math:`k`, the :class:`qibo.backends.hamming_weight.HammingWeightBackend`
a compressed representation of quantum states that is restricted to the subspace of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a verb is missing before a compressed here, right?

Comment on lines +27 to +33
self.platform = engine
if engine == "numpy":
self.engine = construct_backend(self.platform)
elif engine in ["numba", "cupy", "cuquantum"]:
self.engine = construct_backend("qibojit", platform=self.platform)
elif engine in ["tensorflow", "pytorch"]: # pragma: no cover
self.engine = construct_backend("qiboml", platform=self.platform)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so the internal engine is the complete backend, but do you actually need the whole backend or is backend.np enough? Namely, doing self.engine = construct_backend("qibojit", platform=self.platform).np for instance. This was something that we also changed for the clifford backend in the review stage.

Copy link
Contributor

@BrunoLiegiBastonLiegi BrunoLiegiBastonLiegi Apr 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I double checked, the backend is used mostly for the cast method and retrieving the gate matrices, which cannot be circumvented unfortunately. What I was wondering is whether we could dynamically inherit from the correct backend, and make this something similar to our MetaBackend:

from qibo.backends import NumpyBackend

def some_method(self, x):
    return self.np.log(x)

def create_HW_backend(backend):
    hw_backend = type("HWBackend", (backend,), {"some_method": some_method,})()
    hw_backend.name = "hamming_weight"
    return hw_backend

b = create_HW_backend(NumpyBackend)
print(b.np) # this returns numpy
print(b.some_method(2)) # this returns np.log(2)

from qibo import gates
print(gates.x(0).matrix(b)) # this returns the numpy matrix of X

Copy link
Contributor

@BrunoLiegiBastonLiegi BrunoLiegiBastonLiegi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I dug a little deeper, here some more comments. Broadly speaking I think you might benefit from the use of functools.cache instead of manually caching (assuming the number of different combinations is indeed restricted).


return strings

def _get_cached_strings(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the set of possible inputs combinations is restricted, you could even consider caching this one with functools.cache.

Comment on lines +234 to +235

def _get_lexicographical_order(self, nqubits, weight):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _get_lexicographical_order(self, nqubits, weight):
@cache
def _get_lexicographical_order(self, nqubits, weight):

same here

Comment on lines +308 to +311
if (
key_0 not in self._dict_cached_strings_one
or key_1 not in self._dict_cached_strings_one
):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah okay, here happens the caching, by using the @cache decorator directly you could probably avoid this if/else

Comment on lines +568 to +582
for i, index_i in enumerate(indexes):
for j, index_j in enumerate(indexes):
if index_i % 2 ** (
nqubits - gate_qubits - ncontrols
) == index_j % 2 ** (nqubits - gate_qubits - ncontrols):
if (
strings[j][gate_qubits : gate_qubits + ncontrols].count("1")
== ncontrols
):
matrix[i, j] = gate_matrix[
index_i // 2 ** (nqubits - gate_qubits),
index_j // 2 ** (nqubits - gate_qubits),
]
else:
matrix[i, j] = 1 if i == j else 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you make indexes and strings ndarrays and avoid this quite convoluted loop using array operations?

Comment on lines +585 to +599
for i, string_i in enumerate(strings):
new_string_i = [""] * len(string_i)
for k in range(nqubits):
new_string_i[map_[k]] = string_i[k]
new_string_i = "".join(new_string_i)
new_index_i = strings.index(new_string_i)

for j, string_j in enumerate(strings):
new_string_j = [""] * len(string_j)
for k in range(nqubits):
new_string_j[map_[k]] = string_j[k]
new_string_j = "".join(new_string_j)
new_index_j = strings.index(new_string_j)

new_matrix[new_index_i, new_index_j] = matrix[i, j]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly here, if strings is an array life becomes much easier probably

renatomello and others added 2 commits April 7, 2025 05:39
@renatomello renatomello changed the title New backend specialised for Hamming-weight-preserving circuits New backend specialised in Hamming-weight-preserving circuits Apr 7, 2025
Copy link

Run on QPU sim completed! :atom:

You can download the coverage report as an artifact, from the workflow summary page:
https://github.com/qiboteam/qibo/actions/runs/14445183248

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request run-on-sim
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants