Open stavros11 opened 3 weeks ago
Given these points, an alternative solution that does not require adding measurement gates to the Gate
enumerations would be to leave them in the Circuit
API only and provide a circuit.measure
, separate from circuit.add
, with the following signature:
measure(register_name: str, collapse: bool)
I haven't thought this thoroughly but the main motivation is that currently adding measurements is slightly different than adding other gates anyway, since it is returning a reference to the corresponding result (to be replaced by some id?) that can be used to control other gates. Therefore it may be cleaner to have a separate interface altogether.
To add to this, I’d suggest removing the bit flip method entirely and forcing the user to use the ReadoutError channel
On Thu, 13 Jun 2024 at 17:32, Stavros Efthymiou @.***> wrote:
Currently measurement is a gate, but has some additional attributes and arguments compared to other gates, in particular:
- register_name: @alecandido https://github.com/alecandido I believe we have not discussed what to do with that, which is relevant for the creation of results. One potential solution would be to lift to the circuit and keep a map {measurement_gid: register_name}.
- collapse: can be handled as a separate measurement gate (MC) in qibo-core.
- basis: will be left at high-level (not qibo-core) since it is only used to prepend other gates and does not affect execution.
- bitflip probabilities (p0, p1): only affect the state post-execution by introducing bitflips, so it is not needed in qibo-core.
- result: used to condition other gates on measurement outcomes. Will be decoupled from the gate and handled by the circuit.
- pulses: most likely legacy and will be dropped.
— Reply to this email directly, view it on GitHub https://github.com/qiboteam/qibo-core/issues/30, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABH5QVSC7PACP7V3RAEVDVTZHGNOHAVCNFSM6AAAAABJINN7AGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGM2TCMJXGAZTCNY . You are receiving this because you are subscribed to this thread.Message ID: @.***>
To add to this, I’d suggest removing the bit flip method entirely and forcing the user to use the ReadoutError channel
Good point, thanks. I forgot to mention here, but indeed, for mid-circuit measurements noise should be added using channels. From the qibo-core point of view there isn't much to do on this other than properly supporting channels (#27).
As for removing bitflip, I am fine with that, but should be done on the qibo side.
register_name: @alecandido I believe we have not discussed what to do with that, which is relevant for the creation of results. One potential solution would be to lift to the circuit and keep a map
{measurement_gid: register_name}
.
I wonder whether we even need the map inside the Circuit
. In the end, measurement_gid
is already unique, and it is returned when you add the map, so that the user could maintain the map by himself, just for convenience.
Since the map would be an interface feature, I'd consider keeping it in the Qibo Circuit
object, and give up on measurement names within qibo-core
(just identifying them by gid
as any other gate).
Given these points, an alternative solution that does not require adding measurement gates to the
Gate
enumerations would be to leave them in theCircuit
API only and provide acircuit.measure
, separate fromcircuit.add
, with the following signature:measure(register_name: str, collapse: bool)
I haven't thought this thoroughly but the main motivation is that currently adding measurements is slightly different than adding other gates anyway, since it is returning a reference to the corresponding result (to be replaced by some id?) that can be used to control other gates. Therefore it may be cleaner to have a separate interface altogether.
What you're proposing is sensible, but if we keep using the gid
(as proposed in the previous comment) maybe we could even keep add
ing measurement gates, since it's how they will get represented internally anyhow.
But if the Circuit.measure()
interface is convenient enough, we could consider adding it to the high-level Qibo.
Since the map would be an interface feature, I'd consider keeping it in the Qibo
Circuit
object, and give up on measurement names withinqibo-core
(just identifying them bygid
as any other gate).
I agree with this.
What you're proposing is sensible, but if we keep using the
gid
(as proposed in the previous comment) maybe we could even keepadd
ing measurement gates, since it's how they will get represented internally anyhow.
Indeed, if we drop register_name
(from qibo-core) and collapse is just another gate in the enumaration, then .measure
wouldn't be very useful as it should be identical to .add
. So we can just go with two measurement gates (collapse / not collapse) to be used with .add
.
I have been thinking how to implement this but ended up discovering a few further issues/open questions, mainly related to the results and not the measurement gate itself. In summary these are the following:
register_name
). We already discussed not having this here, but this may cause an issue.MeasurementResult
and MeasurementOutcomes
.More details:
For 1:
Let's assume that we completely ignore registers (register_name
s) from qibo-core. Since all backends depend only on qibo-core and not on qibo, the result of backend.execute_circuit(circuit: qibo_core.Circuit, nshots)
will not have registers. On the other hand, I believe (based on earlier discussions with others) that in qibo we would like to maintain the same interface and therefore the result of qcircuit(nshots)
(with qcircuit: qibo.models.Circuit
) will have registers. This is possible, but it creates an asymmetry between circuit execution using backends (low level user) vs using the GlobalBackend
(high level user), and I am not sure if everyone would agree with that. It would also mean that if at some point we decide to drop GlobalBackend
, we lose the registers.
Related, but not entirely relevant to measurements, is whether we want to support something like backend.execute_circuit(qcircuit: qibo.models.Circuit)
. In principle this creates a cyclic dependency as the backends should not depend on qibo, but as discussed previously there are ways around that. In this case, registers would most likely be dropped.
If we do not support registers in qibo-core, it is sufficient to introduce the measurement as a single-qubit gate under the One
enumeration. Otherwise, measurements will need to have variable number of targets which is not supported by the current infrastructure (number of targets of each gate is hardcoded). This will also be a problem for the Unitary
/Matrix
gate but that's for another issue.
For 2:
This is not a qibo-core specific thing, but it mostly came up because MeasurementResult
is currently hanging from the measurement gate, which will no longer be the case, therefore some related refactoring is needed. We could use that opportunity to clean up the relationship between these objects.
What are these objects currently:
MeasurementResult
: container for the samples/frequencies of a single measurement gate (returned by circuit.add(gates.M(...))
).MeasurementOutcomes
: container for the samples/frequencies of all the measurements in the circuit (returned by circuit execution).What are their relationships:
MeasurementOutcomes
is a collection (concatenation) of MeasurementResult
sMeasurementResult
is a view (slice) of MeasurementOutcomes
Why we need both relationships:
MeasurementOutcomes
that we return to the user (case 1).MeasurementOutcomes
and then slice this to update the individual MeasurementResult
s. In principle, this is not required and we could sample each MeasurementResult
individually, but I guess it happens like this because historically in qibo simulation came before hardware and because sampling one array of (nqubits, nshots)
is probably more efficient than sampling nqubits
arrays of (nshots,)
(but difference may be negligible).In practice, now MeasurementOutcomes
holds references to all the associated MeasurementResult
s through the corresponding gates.
Relationship 1 is handled in https://github.com/qiboteam/qibo-core/blob/a5e0ac9c9a043fe81f4741d3c628cf7f480ee3dc/crates/py/qibo_core/result.py#L333-L335
and relationship 2 in https://github.com/qiboteam/qibo-core/blob/a5e0ac9c9a043fe81f4741d3c628cf7f480ee3dc/crates/py/qibo_core/result.py#L364-L366
so MeasurementOutcomes
is essentially mutating its MeasurementResult
s.
The main issue with this approach is that samples are duplicated. I believe it is preferrable to have them in a single place and treat the other as a view, however I am not sure which should be the single place (basically multiple 1D-arrays vs single 2D-array). In hardware execution we will receive the data as 1D arrays from the instrument at some point (I think there is no way around that), but we can still concatenate to 2D and forget the original arrays.
Another point is that currently MeasurementResult
has two roles: the one described above (view of a single gate results) and also to allow using the results of collapse measurements for parametrization of other gates (#32). For the first role, we probably don't even need an object (array is sufficient), for the second we probably need but this will depend on how we handle #32.
Ok, I will now reply to 1., and then read thoroughly 2. after.
Let's assume that we completely ignore registers (
register_name
s) from qibo-core. Since all backends depend only on qibo-core and not on qibo, the result ofbackend.execute_circuit(circuit: qibo_core.Circuit, nshots)
will not have registers.
Registers are not disappearing at all, it's just the aliases that are not supported any longer.
Internally, the backend may want to (or have to) keep track of the connection between the result and the gate.
This could be done explicitly, with a {measurements_gid: result}
mapping, or implicitly, with a [result]
array, and sorting uniquely the [measurement_gids]
, such that the results[gid]
access is realized either by the hashmap, or computed as in results[measurements.index(gid)]
.
On the other hand, I believe (based on earlier discussions with others) that in qibo we would like to maintain the same interface and therefore the result of
qcircuit(nshots)
(withqcircuit: qibo.models.Circuit
) will have registers. This is possible, but it creates an asymmetry between circuit execution using backends (low level user) vs using theGlobalBackend
(high level user), and I am not sure if everyone would agree with that. It would also mean that if at some point we decide to dropGlobalBackend
, we lose the registers.
That's fully a Qibo problem, and we have all the tools to solve that in Qibo. The library could maintain an alias map, and this could be done in the GlobalBackend
, in the qibo.models.Circuit
object, or in a separate Measurements
object that is generated by the Circuit
, and returned to the user.
Though, I have the feeling I might be missing your point. Because I really don't see registers as substantially disappearing.
Only the result stored inside won't be there any longer, but the Circuit
, while executing, can always populate it from the qibo_core
result object, as needed. Or whoever else will implement the wrapping execute_circuit(circuit: qibo.models.Circuit, backend: str/qibo_core.Backend)
function in Qibo (that will certainly be there, since we need at least the circuit translation).
Related, but not entirely relevant to measurements, is whether we want to support something like
backend.execute_circuit(qcircuit: qibo.models.Circuit)
. In principle this creates a cyclic dependency as the backends should not depend on qibo, but as discussed previously there are ways around that. In this case, registers would most likely be dropped.
Answered above. backend
will be a string, or qibo_core
object. We need to implement this function somewhere else, and it could be:
Circuit
itselfBackend
object wrapper in QiboIf we do not support registers in qibo-core, it is sufficient to introduce the measurement as a single-qubit gate under the
One
enumeration. Otherwise, measurements will need to have variable number of targets which is not supported by the current infrastructure (number of targets of each gate is hardcoded). This will also be a problem for theUnitary
/Matrix
gate but that's for another issue.
It would not be a problem in any case. I'm planning to drop One
as soon as we'll face #22, making a single plain enum
.
We can store the information about the number of qubits the gate is referred to in a further attribute in Circuit
. Similar to all the other proposals to replace the gates' content.
We're just moving from the list of objects to the database representation, having all the information as types as plain as possible, with a simple container objects, handled by a single manager, the Circuit
.
Ok, about 2. I believe there is only one complex point, everything else is "easily" solved: let's use a single 2D-array, and let's make it an array. At least in qibo-core
.
About the Qibo interface, I would worry at a second stage, and it's always possible to fix it: if qibo-core
stores enough information, as all the backends do, we'll always be able to handle and reshape it in Qibo, as we're now doing one backend at a time. The price is just wrapping.
Another point is that currently
MeasurementResult
has two roles: the one described above (view of a single gate results) and also to allow using the results of collapse measurements for parametrization of other gates (#32). For the first role, we probably don't even need an object (array is sufficient), for the second we probably need but this will depend on how we handle #32.
To be fair, this is more is not even that much related to qibo-core
, but to the backends implementing the collapse in simulation. Qibotn won't support mid-circuit collapse (to the best of my understanding, it doesn't make much sense to use tensor networks for that). Qibolab will have to handle it differently anyhow, despite supporting it, since the results to be used will live on the boards, and they are not going back and forth to qibo-core
. Possibly, same story for GPU, where it would be convenient to hold them on the device, without downloading (if possible). External backends (Qulacs and cloud) are maintaining their own, of course.
Thus, I'd treat the problem as backend-specific, and each backend will store it in the place which is most convenient for it. The current mechanism is definitely tuned for the NumpyBackend
, and extended to all the other ones. I would code this as part of the CPU backends library, whenever we'll implement it.
Currently measurement is a gate, but has some additional attributes and arguments compared to other gates, in particular:
{measurement_gid: register_name}
.MC
) in qibo-core.