-
Notifications
You must be signed in to change notification settings - Fork 2.5k
supporting non-string identifiers #308
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
Conversation
test/python/test_identifiers.py
Outdated
}] | ||
} | ||
self.qp_program_finished = False | ||
self.qp_program_exception = Exception() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you double-check if these are needed in all the test cases? They were meant to be for some specific async-related tests on test_quantumprogram
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 91e78ae
qiskit/_register.py
Outdated
self.name = name | ||
self.size = size | ||
self.oq_name = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If self.oq_name
depends only on self.name
and it is always the same during the lifetime of the object, can we set its value during __init__
directly, instead of waiting until the openqasm_name()
calls happen, avoiding one extra layer of complexity?
Additionally, can you rename the variable to something more descriptive (perhaps the full openqasm_name
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because there is no need if the openqasm representation is never used (and we are trying to go in that direction).
I changed oq_name
for _openqasm_name
(to avoid collision the method) (in ef7e1fd).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
openqasm_name()
might be a property
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in e5a5fe4
qiskit/_register.py
Outdated
self.oq_name = oq_name | ||
return oq_name | ||
self.oq_name = self.name | ||
return str(self.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to the comment on __init__()
- if that comment makes sense, we can probably simplify quite a bit the function, perhaps making it static (ie. def some_good_name(name)
).
qiskit/_quantumcircuit.py
Outdated
@@ -37,6 +37,7 @@ class QuantumCircuit(object): | |||
|
|||
def __init__(self, *regs): | |||
"""Create a new circuit.""" | |||
self.name = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense (conceptually) for self.name
to hold the value None
?
It seems to have the value None
only for a brief moment, and to be always modified manually after its creation during create_circuit()
. If that is the case, I'm wondering if we could actually consider name
to be a required argument passed to the constructor directly: this way we would avoid ambiguity and lessen the risk of having the name in an inconsistent state. The major downside would be that we might break backwards compatibility (although in this case, it might not have such a big impact, as instantiating circuits is probably quite low-level).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the name is not given in QuantumCircuit
construction time, but when the circuit is added to the quantum program. Yeap, I dont like that the circuits dont have a name per se neither. But I think in this way the original approach is kept (even when I'm not agreeing with it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you are adding a name
attribute to the class, you are effectively saying that "the circuits have a name" - and arguably modifying the original approach. Can you check the real impact of adding it to the constructor (as far as I can see, outside of the SDK there is only one tutorial that would need to be updated)? I feel that we should either modify the class completely, or not modify it at all, but standing in middle ground is not ideal (QuantumCircuit.name
becomes a variable that is not really set by the class itself).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
qiskit/_quantumprogram.py
Outdated
name = qrms[0] | ||
else: | ||
raise QISKitError("You have to select a quantum register to get when there is more" | ||
"than one available") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: perhaps we can generalize this pattern in a small function name = get_name_if_there_is_only_one_item_or_raise_an_error(some_list)
? It seems to be used in a number of places - not sure if they are enough, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went for get_single_name
, it seems shorter :) (see a011417)
@@ -387,14 +387,20 @@ def destroy_circuit(self, name): | |||
raise QISKitError("Can't destroy this circuit: Not present") | |||
del self.__quantum_program[name] | |||
|
|||
def add_circuit(self, name, quantum_circuit): | |||
def add_circuit(self, name=None, quantum_circuit=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in private, the side effect of making an argument of the public API optional when it is not really optional is not ideal ... but still, I'm not sure about the best way of avoiding it. Tricky one!
qiskit/_quantumprogram.py
Outdated
for i in range(10000): | ||
if formatstring % i not in existing: | ||
return formatstring % i | ||
raise QISKitError("Anonymous identifier was not possible to be created") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of the explicit loop, perhaps it makes sense to use a generator (or several) or similar, maybe with the help of itertools.count?
>>> some_counter = itertools.count()
>>> next(some_counter)
0
>>> next(some_counter)
1
>>> next(some_counter)
2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
range()
does not requires imports, creates an object range
and there is a limit to the looping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Importing from the standard lib is "free", so it should not really weight too much in the decision - and actually the limit to the looping is not the best of practices, as it introduces some "arbitrary magic number" ™️ . If the standard library provides a pythonic way of solving a problem (in this case, generating sequential numbers), why not take advantage of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 41a20cd2948464d52c14b630de1c2aa28467bca7
I spent too much time trying this: qp.create_quantum_registers([{"quantum_registers": [ {"name": "qr", "size": 4} ] }]) unify format of the docstrings and general format fixes #290 create_program_with_specsnonames create_anonymous_classical_register create_anonymous_quantum_register create_classical_registers_noname create_quantum_registers_noname create_circuit_noname get_register_and_circuit_names_nonames get_qasm get_qasms reasonable defaults get_qasms_noname get_execution_list_noname change_circuit_qobj_after_compile_noname add_circuit_noname old QPS_SPECS gone convert the names to openqasm names when thats needed names can be ints tuples as id are also supported lint and style debugging appveyor Hash randomization causes the iteration order of dicts and sets to be unpredictable and differ across Python runs typ0!!! I forgot how to math checks about 0 and emtpy strings as identifiers better using id instead of hash lenght of the output considers short ids cleaning up self.oq_name -> self._openqasm_name get_single_name quantumcircuit name is set at new-time, not after test adjusted name arg counter to track automatic identifiers docstring and format openqasm_name as a property assertEqual -> assertCountEqual
* Tweak docstrings and exception messages when using non-string identifiers, for clarity towards the end user and for correctly indicating that the functions can use hashables. * Revise `get_single_name()`, renaming it and documenting in a more general way and making it privat-ish. * Revise `create_id()`, using strings directly as parameter rather than format strings, to avoid a potential source of errors. * Explicitly check that `add_circuit()` receives a valid `quantum_circuit`, preserving the previous behaviour. Note that this implies that the attribute is not really optional (even if declared optional by the signature).
Thanks, @1ucian0 ! I added a commit with a few minor changes plus rebasing, and finally merging. In practice it means that now identifiers for different entities (circuits, registers, etc) can be omitted while building the QuantumProgram, which hopefully will be more friendly towards developers. Nice! |
* Support for non-string identifiers I spent too much time trying this: qp.create_quantum_registers([{"quantum_registers": [ {"name": "qr", "size": 4} ] }]) unify format of the docstrings and general format fixes Qiskit#290 create_program_with_specsnonames create_anonymous_classical_register create_anonymous_quantum_register create_classical_registers_noname create_quantum_registers_noname create_circuit_noname get_register_and_circuit_names_nonames get_qasm get_qasms reasonable defaults get_qasms_noname get_execution_list_noname change_circuit_qobj_after_compile_noname add_circuit_noname old QPS_SPECS gone convert the names to openqasm names when thats needed names can be ints tuples as id are also supported lint and style debugging appveyor Hash randomization causes the iteration order of dicts and sets to be unpredictable and differ across Python runs typ0!!! I forgot how to math checks about 0 and emtpy strings as identifiers better using id instead of hash lenght of the output considers short ids cleaning up self.oq_name -> self._openqasm_name get_single_name quantumcircuit name is set at new-time, not after test adjusted name arg counter to track automatic identifiers docstring and format openqasm_name as a property assertEqual -> assertCountEqual * Fixes for support of non-string identifiers * Tweak docstrings and exception messages when using non-string identifiers, for clarity towards the end user and for correctly indicating that the functions can use hashables. * Revise `get_single_name()`, renaming it and documenting in a more general way and making it privat-ish. * Revise `create_id()`, using strings directly as parameter rather than format strings, to avoid a potential source of errors. * Explicitly check that `add_circuit()` receives a valid `quantum_circuit`, preserving the previous behaviour. Note that this implies that the attribute is not really optional (even if declared optional by the signature).
This PR allows using any kind of hashable (or
None
!) as a circuit name or register name.Description
qp.create_quantum_register(2, 3)
creates a 3-bit long quantum register with name2
, the integer.id := [a-z][A-Za-z0-9_]*
, p22), no-string-typed identifiers are converted to this format when they are dump to QASM. This conversion is notified via logger (at info level) and is only performed once per identifier.qr = qp.create_quantum_register(size=3)
creates a 3-bit long quantum register without any specific name. Internally, a string-typed name is created (which do not collide with existing names) and can be fetch withqr.name
.QuantumProgram
andResult
to be more intuitive in some methods. For example, if you executed only one circuit,get_counts()
will return the counts for that circuit without requiring the name.Motivation and Context
One of the goals of QISKit is to provide a high-level interface to quantum programmers. Since it was so harnessed to OpenQASM it was natural to lift some of the QASM details to the user-space. As QISKit is moving out from this attachment (see, for example, #273), many of the low-level details are not necessary to expose any more. At least by default. Identifiers are one of those details.
Coders starting with QISKit see identifiers are unnecessarily or limiting (see #290). They are counter-intuitive for first timers. This PR allows, for example, to simplify the HelloWorld program from qiskit.org to:
In addition, it pushed the OpenQASM identifier limitations to the point where it is relevant, i.e. when the high-level code needs to be converted to a QASM format. Identifiers can be almost anything now (fixes #290).
How Has This Been Tested?
This PR does not break backwards compatibility.
New tests were added to show and test the extra functionality.
Types of changes