projectq-framework / projectq Goto Github PK
View Code? Open in Web Editor NEWProjectQ: An open source software framework for quantum computing
Home Page: https://projectq.ch
License: Apache License 2.0
ProjectQ: An open source software framework for quantum computing
Home Page: https://projectq.ch
License: Apache License 2.0
to reproduce, modify the quantum_random_numbers.py
to have
raise Exception("...")
just above the Measure | q1
operation
i get the following
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "/home/noon/dev/ProjectQ/projectq/cengines/_main.py", line 133, in <lambda>
self._delfun = lambda x: x.flush(deallocate_qubits=True)
File "/home/noon/dev/ProjectQ/projectq/cengines/_main.py", line 227, in flush
qb.__del__()
File "/home/noon/dev/ProjectQ/projectq/types/_qubit.py", line 114, in __del__
self.engine.deallocate_qubit(self)
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 151, in deallocate_qubit
self.send([Command(self, Deallocate, ([qubit],))])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_tagremover.py", line 56, in receive
self.send([cmd])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 236, in receive
self._cache_cmd(cmd)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 222, in _cache_cmd
self._check_and_send()
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 200, in _check_and_send
self._send_qubit_pipeline(i, len(self._l[i]))
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 78, in _send_qubit_pipeline
self.send([il[i]])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_replacer/_replacer.py", line 188, in receive
self._process_command(cmd)
File "/home/noon/dev/ProjectQ/projectq/cengines/_replacer/_replacer.py", line 124, in _process_command
self.send([cmd])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_tagremover.py", line 56, in receive
self.send([cmd])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 236, in receive
self._cache_cmd(cmd)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 222, in _cache_cmd
self._check_and_send()
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 200, in _check_and_send
self._send_qubit_pipeline(i, len(self._l[i]))
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 78, in _send_qubit_pipeline
self.send([il[i]])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/backends/_sim/_simulator.py", line 181, in receive
self._handle(cmd)
File "/home/noon/dev/ProjectQ/projectq/backends/_sim/_simulator.py", line 147, in _handle
self._simulator.deallocate_qubit(ID)
RuntimeError: Error: Qubit has not been measured / uncomputed! There is most likely a bug in your code.
Exception ignored in: <bound method Qubit.__del__ of <projectq.types._qubit.Qubit object at 0x7fcce59c5f60>>
Traceback (most recent call last):
File "/home/noon/dev/ProjectQ/projectq/types/_qubit.py", line 114, in __del__
self.engine.deallocate_qubit(self)
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 151, in deallocate_qubit
self.send([Command(self, Deallocate, ([qubit],))])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_tagremover.py", line 56, in receive
self.send([cmd])
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 236, in receive
self._cache_cmd(cmd)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 222, in _cache_cmd
self._check_and_send()
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 194, in _check_and_send
self._optimize(i)
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 136, in _optimize
inv = self._l[idx][i].get_inverse()
File "/home/noon/dev/ProjectQ/projectq/ops/_command.py", line 133, in get_inverse
cmd = Command(self._engine, projectq.ops.get_inverse(self.gate), self.qubits)
File "/home/noon/dev/ProjectQ/projectq/ops/_metagates.py", line 117, in get_inverse
return gate.get_inverse()
File "/home/noon/dev/ProjectQ/projectq/ops/_basics.py", line 215, in get_inverse
return deepcopy(self)
File "/home/noon/tools/anaconda3/envs/projectq/lib/python3.5/copy.py", line 174, in deepcopy
rv = reductor(4)
AttributeError: 'NoneType' object has no attribute '__newobj__'
Exception ignored in: <bound method MainEngine.__del__ of <projectq.cengines._main.MainEngine object at 0x7fccfded00f0>>
Traceback (most recent call last):
File "/home/noon/dev/ProjectQ/projectq/cengines/_main.py", line 143, in __del__
File "/home/noon/dev/ProjectQ/projectq/cengines/_main.py", line 229, in flush
File "/home/noon/dev/ProjectQ/projectq/cengines/_main.py", line 214, in receive
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
File "/home/noon/dev/ProjectQ/projectq/cengines/_tagremover.py", line 56, in receive
File "/home/noon/dev/ProjectQ/projectq/cengines/_basics.py", line 191, in send
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 232, in receive
File "/home/noon/dev/ProjectQ/projectq/cengines/_optimize.py", line 136, in _optimize
File "/home/noon/dev/ProjectQ/projectq/ops/_command.py", line 133, in get_inverse
File "/home/noon/dev/ProjectQ/projectq/ops/_metagates.py", line 117, in get_inverse
File "/home/noon/dev/ProjectQ/projectq/ops/_basics.py", line 215, in get_inverse
File "/home/noon/tools/anaconda3/envs/projectq/lib/python3.5/copy.py", line 174, in deepcopy
File "<frozen importlib._bootstrap>", line 969, in _find_and_load
File "<frozen importlib._bootstrap>", line 954, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 887, in _find_spec
TypeError: 'NoneType' object is not iterable
i don't think this is good. i'm guessing the cleanup code just needs a bit of a clean up :)
def receive(self, command_list):
...
new_command_list = [self._cmd_mod_fun(cmd) for cmd in command_list]
^^^^^^^^^^^ unused
self.send(command_list)
There also seems to be an issue where some wires are omitted after a swap gate is drawn.
With the current settings of coveralls.io, code coverage can decrease if a function is refactored with less lines of code as before, see e.g. Pull request #15. This shouldn't be indicated as a possible failure.
I haven't found a setting in coveralls.io to change to incremental code coverage reports. However, it is possible to check code coverage line by line on their website to check what is going on.
We might want to consider switching to a different tool, e.g. codecov which does handle such cases:
https://docs.codecov.io/docs/codecov-delta
When writing a test to reproduce a bug, it is helpful to be able to force particular measurement results. A post-select operation that magically discarded half of the amplitude vector and renormalized the remainder would make this possible.
I want to allow users to allocate qubits, or to ask for qubits at particular positions on a grid. So I have a method qubit_at(self, x, y)
in addition to the allocate
method, and qubit_at
returns a FixedPositionQubit
.
But when commands are applied to FixedPositionQubit
s, those commands don't show up back in the engine with target qubits of type FixedPositionQubit
. Instead, commands strip out all the type information and store WeakQubitPtr
instances.
Stripping type information like this is not pythonic; it breaks duck typing.
Is there a workaround for this? I could pack all the information into the id, but that feels really over the top.
There are operations that have a well-defined behavior for any number of target qubits, including 0. For example, an increment operation performs a -> (a+1) % 2**n
. When n=0
, it reduces to the trivial identity operation 0 -> 0
.
But if I define an increment gate and apply it to no qubits, instead of nothing happening I get an exception:
> return Command(qubits[0][0].engine, self, qubits),
E IndexError: list index out of range
Of course most people won't trigger this on purpose, but empty cases have a knack of appearing when code interacts with other code and handling them gracefully saves everyone time. For example a decomposition that needs to increment n-k
of the qubits in a register should work for k
qubits without any issue, but because of this bug the decomp would really only work for k+1
qubits unless a special case was added.
This do-nothing test:
def test_repetition():
engine_list = []
for _ in range(2):
backend = Simulator()
eng = MainEngine(backend=backend, engine_list=engine_list)
eng.allocate_qureg(1)
_, state = backend.cheat()
Fails with this error:
self = <projectq.backends._sim._pysim.Simulator object at 0x7ff7ff10b5d0>, ID = 0
def allocate_qubit(self, ID):
"""
Allocate a qubit.
Args:
ID (int): ID of the qubit which is being allocated.
"""
self._map[ID] = self._num_qubits
self._num_qubits += 1
> self._state.resize(1 << self._num_qubits)
E ValueError: cannot resize an array that references or is referenced
E by another array in this way. Use the resize function
projectq/backends/_sim/_pysim.py:112: ValueError
I think it has something to do with cache invalidation, because if I use a new empty-engine list for each call then the test runs to completion.
Compilation of the C++ backend currently has a hard-coded '-march=native' option as part of the setup instructions. This causes problems when a single python environment is used across multiple machines with different hardware; importing ProjectQ will cause immediate kernel death with no error message.
Would it be possible to add a setup flag for this instead of hard-coding?
Can anyone Tell me
the uses of this framwork .
Our installation breaks since yesterday as by default we are using the latest pybind11 which is now v2.2.0.
A workaround for the moment is to install pybind v2.1.1 (pip install pybind==2.1.1
) prior to installing ProjectQ until we have fixed the compatibility issues.
The build failure can be seen in #142 (or any other pull request after rebuilding it)
These constructs save several boilerplate in the grover example. For example:
def run_grover(eng, n, oracle):
x = eng.allocate_qureg(n)
All(H) | x
with Loop(eng, int(math.pi/4.*math.sqrt(1 << n))):
oracle(eng, x)
with PlusControl(eng, x):
eng.PhaseFlip()
Measure | x
eng.flush()
return [int(qubit) for qubit in x]
def alternating_bits_oracle(eng, qubits):
with Control(eng, qubits[1::2]) and OffControl(eng, qubits[::2]):
eng.PhaseFlip()
An OffControl
requires that the qubit be |0>. It's equivalent to X-before, Control-during, X-after.
A PlusControl
requires that the state be |0>+|1>. It's equivalent to H,X-before, Control-during, X,H-after.
C(X).generate_command(q)
doesn't return a command equal to what C(X) | q
sends to q
's engine.
Also, note that the implementation of projectq.ops.ControlledGate.__or__
imports projectq.meta. This is a cyclic dependency. projectq.meta depends on projectq.ops.
I'm using Python 3.6.0 and the latest source code from master.
In the examples folder, running quantum_random_numbers.py gives
(Note: This is the (slow) Python simulator.)
Measured: 0
Now try to use the IBM backend. Running quantum_random_numbers_ibm.py gives
IBM QE user (e-mail) > [email protected]
IBM QE password > x
I enter the login information, and receive an email message:
Sorry, the results of the execution of your quantum score projectq_experiment failed.
You can see the experiment code accessing to the next link:
https://quantumexperience.ng.bluemix.net/qstage/#/editor?codeId=7f838b4658f61d1d805cb31f584a5552
Thank you in advance for your help making IBMโs quantum presence on the web as exciting and cool as possible,
Sincerely,
the IBM Quantum Team
The link points to a picture of the quantum circuit that looks correct.
After a minute or so, I get the error
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/backends/_ibm/_ibm.py", line 232, in _run
data = res['data']['p']
TypeError: 'NoneType' object is not subscriptable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "quantum_random_numbers_ibm.py", line 18, in
eng.flush()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_main.py", line 229, in flush
self.receive([Command(self, FlushGate(), ([WeakQubitRef(self, -1)],))])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_main.py", line 214, in receive
self.send(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_tagremover.py", line 56, in receive
self.send([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_optimize.py", line 234, in receive
self.send([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_replacer/_replacer.py", line 190, in receive
self.send([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_tagremover.py", line 56, in receive
self.send([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_ibmcnotmapper.py", line 194, in receive
self._run()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_ibmcnotmapper.py", line 147, in _run
self.next_engine.receive([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_optimize.py", line 234, in receive
self.send([cmd])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/cengines/_basics.py", line 191, in send
self.next_engine.receive(command_list)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/backends/_ibm/_ibm.py", line 275, in receive
self._run()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/projectq/backends/_ibm/_ibm.py", line 262, in _run
raise Exception("Failed to run the circuit. Aborting.")
Exception: Failed to run the circuit. Aborting.
I also found same issue with the pip installed version.
Any advice would be appreciated.
In MainEngine, there is this line:
self._delfun = lambda x: x.flush(deallocate_qubits=True)
atexit.register(self._delfun, self)
Why is this done? It seems like anytime this would get triggered and matter, something very bad must have happened (e.g. printing gate stats without flushing the engine first) and we're about to compound the problem by running more logic on a known-corrupted state.
This line also acts as a de-facto memory leak in python 2. In a long ipython session there will be more and more engines kept alive by this logic. Those engines may have cached transformations or collected stats that use enough memory to matter.
Also it can cause code to run after tests fail, potentially writing debug data unrelated to the failure into the console, swamping out the actual failure reason (e.g. I've noticed test failures usually trigger a wall of warnings about qubits not being deallocated properly).
Many files in the project have spaces hidden amongst the tabs. Sometimes by accident, sometimes on purpose for fine-grained alignment (despite that not making any sense with tabs present).
Given that PEP8 strongly recommends spaces, we should probably switch to spaces.
The most important condition that hash functions must satisfy is a == b implies hash(a) == hash(b)
, but the BasicQubit class doesn't meet it:
a = _qubit.BasicQubit(fake_engine, 5)
b = _qubit.BasicQubit(fake_engine, 5)
assert a == b
assert hash(a) == hash(b)
The existing tests are checking for inequality instead of equality, i.e. that a != b implies hash(a) != hash(b)
. Which is actually not a requirement of hash functions at all, though it matters for performance.
I'm guessing that pybind11 is finicky in some way I don't grok. It's strange that there are install instructions to explicitly not include the cpp backend, yet it has tests that still fail under that setup.
Add +=
, ^=
, etc operators to Qureg:
class Qureg(list):
...
def __iadd__(self, other):
if isinstance(other, int):
OffsetGate(other) | self
return self
if isinstance(other, Qureg):
Add | (other, self)
return self
if isinstance(other, RValue):
other.add_into(self)
return self
raise NotImplementedError()
def __ixor__(self, other):
if isinstance(other, Qureg):
for dst, src in zip(self, other):
X & src | dst
return self
if isinstance(other, int):
for i in range(len(self)):
if (other >> i) & 1:
X | self[i]
return self
if isinstance(other, RValue):
other.xor_into(self)
return self
raise NotImplementedError()
...
Also add RValue-producing +
, ~
, &
etc operators:
class Qureg(list):
def __invert__(self):
return RValueNotQureg(self)
def __neg__(self):
return RValueNegQureg(self)
def __and__(self, other):
return RValueAndQuregs((self, other))
Add RValue classes:
class RValueAndQuregs(RValue):
def __init__(self, quregs):
self.quregs = quregs
def xor_into(self, target_qureg):
for dst_bit, src_bits in zip(self, zip(*self.quregs)):
X & src_bits | dst_bit
...
And then users can do arithmetic in a way that looks quite like normal python:
def multiply_accumulate(src_qureg, factor, dst_qureg):
i = 0
while factor >= 1 << i:
if (factor >> i) & 1:
dst_qureg += src_qureg << i # Generates command(s)
i += 1
Currently, the example code in grover.py has this:
with Control(eng, x[0:-1]):
Z | x[-1]
But it would be clearer to do something like this:
with Control(eng, x):
PhaseFlip | []
Where PhaseFlip
is a gate defined like this:
class GlobalPhase(SelfInverseGate):
def __init__(self, phase_factor):
super().__init__()
self.phase_factor = phase_factor
def __str__(self):
return str(self.phase_factor)
@property
def matrix(self):
return np.matrix([[self.phase_factor]])
PhaseFlip = GlobalPhase(-1)
This gate is a bit unusual, since it has no targets. That's why I opened this issue; I assume it'll break all kinds of code if I use it. But many algorithms get minor simplifications from the "just change the phase of all states meeting the current controls" operation.
For example, the alternating_bits_oracle
has an output parameter. But it really doesn't need one:
def alternating_bits_oracle(eng, qubits):
with Compute(eng):
All(X) | qubits[1::2]
with Control(eng, qubits):
PhaseFlip | []
Uncompute(eng)
To implement a global phase gate, you just arbitrarily pick one of the controls and apply a Z rotation to it. If there's no controls to pick from, just do nothing.
This would give a workaround for the cases where the uncompute-didn't-work warning is annoying, without tossing the usefulness of the warning.
Not sure what the parameter should be called, though.
with Compute(..., expect_decoherence=True):
with Compute(..., will_trash_ancilla=True):
with Compute(..., ignore_trashed_ancilla_warning=True):
This would be useful for testing circuits. Particularly for circuits that happen to be classical, since then a single run of the simulator can be used to test every case by comparing amplitudes in the output vector to the amplitude it should have come from in the input vector.
I kind of like being able to write this:
X & control | target
Instead of this:
with Control(control):
X | target
Here's the code I used to add the functionality.
In BasicGate:
def __and__(self, other):
return ControlledGate(self, other)
And here's ControlledGate:
class ControlledGate:
def __init__(self, gate, control):
self.control = control
self.gate = gate
def __and__(self, other):
return ControlledGate(self, other)
def __or__(self, target):
with Control(target.engine, [self.control]):
self.gate | target
What do you think?
An issue that has come up for a few of us is that it is inconvenient to access the wavefunction in ProjectQ. I am aware that one can call .cheat() to get a dictionary mapping qubit indices in the stored wavefunction to the actual tensor factors in the state vector, together with an array of the amplitudes. From this information it is possible to reconstruct the wavefunction. But it is not intuitive and we don't want users calling a function called ".cheat()" for such important functionality.
I understand why it is unnatural for ProjectQ to provide the entire wavefunction. And I appreciate that you can never "peak" at the entire wavefunction like this when using an actual quantum computer so we are asking more of the ProjectQ simulator than we would ever ask of a real quantum device. But in practice people want to do this. I think it would be great to provide some simple function that gives the user the wavefunction vector in a convenient ordering. This should not be difficult to implement.
The mutability of the controls on a command is creating weird dependencies across API boundaries.
For example, submitting a command to an engine can cause it to have new controls. Very surprising, very easy to cause bugs.
Consider this code:
def coroutine(eng, q):
with Control(eng, q[0]):
yield 1
def driver(eng, q):
for e in coroutine(eng, q):
Z | q[1]
The user will expect the Z operation to not be controlled, but it will instead be controlled by q[0].
A more plausible-in-practice repro is something like this:
async def do_work(eng, q):
with Control(eng, q[0]):
X | q[1]
b = await measure_async_result(q[2])
if b:
X | q[3]
async def driver(eng, regs):
r = all_complete(do_work(eng, q) for q in regs)
eng.flush()
await r
The resulting behavior will be a giant mess, because each asynchronous method will be leaking controls into the others.
I think this may have been mentioned before but it is now becoming a bottleneck for two different ongoing projects that I am involved with. Ideally, one should be able to provide a vector as input. I realize this isn't something that can be done with an actual quantum computer as the backend but it is something that people expect of a simulator.
Some classical operations need to know the size of the register they are applying to. For example: left-rotate, right-rotate, reverse, etc (anything that involves permuting bits).
There are also operations such as 3**(2**k) (mod 2^n)
that grow very large very fast if you don't know n
.
An engine is like a compiler pass. It makes no sense that a compiler pass "breaks" after you use it once, so it can't be used again in another compilation call.
Anything and everything about engines that violates the ability to treat them as re-usable pieces should be separated out.
For example, pylint would have caught #19 via an undefined variable warning.
with Control(eng, ctrl_qubit):
is basically always equivalent to:
with Control(ctrl_qubit.engine, ctrl_qubit):
I think. Otherwise the code is wrong. So might as well pull out that value inside the control constructor:
with Control(ctrl_qubit):
Of course you need to check for lists, etc.
When you apply:
All(X) | targets
The engine receives X commands via the or implementation in Tensor.
But if you apply:
C(All(X)) | (control, targets)
Then then the or operation is bypassed and the engine receives a Tensor command instead.
Test on the master branch and following files.
controlledU.tar.gz
Running the file controlled_gate_test_HL.py gives the controlled gate for the matrix (X gate) this is the CX gate, this is great. However, if I test this CX gate on the IBM engine I get errors in file controlled_gate_test_LL.py.
I would like to have a test file with input a matrix 2 dim and output an IBM qasm file. Is this possible?
Based on to the answer to #85, the reason MainEngine.__del__
exists and the reason atexit
is used in MainEngine is to avoid issues caused by qubits being deallocated during sudden shutdowns (e.g. user hitting ctrl+C).
There are actually several places in the code doing acrobatics to work around the issue. And anytime a test fails, you tend to see a bunch of extra failures get tacked on due to the qubit deallocations running over code that failed halfway through in a bad state.
For example, here is a failing repro test:
def test_qubit_deallocate_reentrancy_safety():
class InjectedBugEngine(_basics.BasicEngine):
def __init__(self):
_basics.BasicEngine.__init__(self)
self.was_in_middle_of_receive = False
self.x = []
def receive(self, command_list):
assert not self.was_in_middle_of_receive
self.was_in_middle_of_receive = True
for cmd in command_list:
assert not isinstance(cmd.gate, DeallocateQubitGate)
if not isinstance(cmd.gate, AllocateQubitGate):
self.x[1] = None # Trigger IndexError
self.was_in_middle_of_receive = False
bug_eng = InjectedBugEngine()
eng = MainEngine(backend=bug_eng, engine_list=[])
try:
q = eng.allocate_qubit()[0]
try:
H | q # Triggers injected IndexError bug.
finally:
q.__del__() # Force dealloc of q, as if it went out of scope.
except IndexError:
pass # Expected.
assert bug_eng.was_in_middle_of_receive
This test fails not once but twice. Both times on the assert not self.was_in_middle_of_receive
line. One due to the manual call to q.__del()__
, and another in atexit
while the interpreter is shutting down.
To fix this problem, we need to make engines re-entrant safe against qubit deallocations. I suggest introducing a defer_deallocate_qubit
method that puts the deallocate command into a queue. Then, before each non-reentrant call to receive, the engine drains that queue.
So, during shutdown, the command will go into a queue and just not do anything. But during actual use, the user will call Allocate or Flush or H | something
, and the deallocate will appear in the same order as usual. The main change will be that circuits may be missing deallocates at the end, but those deallocates don't matter anyways.
More concretely, I'm going to make all of these fixes (most have to happen at the same time because they depend on each other):
already_deallocated
field to Qubit, and use it in __del__
so that it can't re-enter itself anymore.DirtyQubitTag
to cengines (but still expose it from meta) so there's no in-method imports needed to create dirty deallocate commands.drain_deferreds_and_receive
to BasicEngine that drains deferred commands before calling receive
.receive
calls to either send
or drain_deferreds_and_receive
.The reason I'm adding a new method, instead of replacing receive
, is that this method is not intended to be overridden. It's a breaking change that should be considered separately.
Can't quite figure out what the intended action is, but the write to cmd.tags
keeps being overwritten by future iterations in a way that looks suspicious.
for cmd in command_list:
for tag in self._tags:
cmd.tags = [t for t in cmd.tags if not isinstance(t, tag)]
^^^^^^^^^ only last iteration of loop matters
self.send([cmd])
Because pi
is transcendental, involving it in the definition of gates forces floating point error even when performing rational fractions of a whole turn.
So, instead of a transcendental angle, I suggest we use a fractional exponent.
Something like this:
class ZPow(BasicRotationGate):
def __init__(self, exponent):
BasicGate.__init__(self)
self.exponent = (exponent + 1) % 2 - 1
def __str__(self):
return "Z**(" + str(self.exponent) + ")"
def tex_str(self):
return "Z^{" + str(self.exponent) + "}"
def get_inverse(self):
return ZPow(-self.exponent)
def get_merged(self, other):
if isinstance(other, ZPow):
return ZPow(self.exponent + other.exponent)
raise NotMergeable("Can't merge different types of rotation gates.")
def __eq__(self, other):
return isinstance(other, ZPow) and other.exponent == self.exponent
def __ne__(self, other):
return not self.__eq__(other)
@property
def matrix(self):
# Note: expiTau should ensure quarter turns are exact
return np.matrix([[1, 0], [0, expiTau(self.exponent)]])
Note that passing in a Fraction
instead of a float
will work fine.
A secondary benefit of this change is that the names of gates become easier to read. I tweaked the Shor example to use ZPow(Fraction(1, 1 << (k - i)))
instead of R(-math.pi/(1 << (k - i)))
, and this is the resource count:
CMultiplyByConstantModN(1, 35) : 11
CMultiplyByConstantModN(6, 35) : 1
Deallocate : 7
H : 24
Measure : 13
X : 11
Z**(1/2) : 1
Z**(1015/1024) : 1
Z**(119/128) : 1
Z**(2039/2048) : 1
Z**(23/32) : 1
Z**(247/256) : 1
Z**(3/4) : 1
Z**(503/512) : 1
Z**(55/64) : 1
Z**(7/16) : 1
Z**(7/8) : 1
Max. width (number of qubits) : 7.
7 * 5 = 35
Which is a lot clearer than this:
...
R(10.9955742876) : 1
R(11.0569335191) : 1
R(11.3882733693) : 1
R(11.8116520667) : 1
R(12.1890113405) : 1
R(9.5474964238) : 1
R(9.67021488683) : 1
R(9.91565181289) : 1
...
(It also makes much nicer latex circuit output.)
Because most activity happens on develop, it's usually the one I want to be looking at / merging into / etc. For looking at specific releases I'd go to the release tab and see the tags.
For example:
H = Y**0.25 @ Z @ Y**-0.25
H | q # applies Y**-0.25 then Z then Y**0.25
IBM's QE chip now has a bow tie connectivity graph and we should extend the mapper to be able to use these additional connections.
Something like
If mapping is impossible without swaps: throw exception (for now).
They add notification noise and comment noise. There's already a place for this kind of per-commit tooling information: the check status messages.
Plus most of the existing message is discussing information we already know (i.e. the branches and commits involved in the pull request).
The simulator back-end already has a cheat() function to access the wavefunction and hence it is possible to calculate expectation values when using the simulator back-end. But it would be nice to have a few helper functions to make it easier to use and also an example in the tutorials.
We should automatically check whether all examples run fine (in addition to pytest).
For testing decompositions, it's often useful to prevent AutoReplacer from passing along complicated gates. This functionality could go into DummyEngine, but separating it out keeps DummyEngine simple.
See #54 for the basic idea.
When I run the install commands given in the documentation:
python -m pip install --user projectq
or:
sudo apt-get install build-essential python3 python3-pip
sudo pip3 install --user projectq
The install fails with multiple distinct errors:
[...]
ImportError: No module named pybind11
[...]
Command [...]/bin/python -c "import setuptools, tokenize;[...] failed with error code 1 [...]
[...]
File "[...]/pip/basecommand.py", line 161, in main
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 42: ordinal not in range(128)
I think the first two errors are because the documentation says I need to install "build tools" but doesn't actually say what they are or how to install them. So I wasn't able to do that. (I do have g++
installed.) But the third error looks typical of a python2 data-vs-str encoding issue.
I've noticed the All operation has some pretty strange error checking. This is on line 277 of ProjectQ/projectq/ops/_metagates.py. (Looking at develop for reference.)
There, __or__
tries to apply a gate to multiple qubits, but a bunch of the checks seem unnecessary or lead to failures later on. The first line does some work to check if All acts on a tuple of qubits, in which case it converts the tuple of qubits to the value of the first one, and then raises an AssertionError on line 282. More generally, All(op) | register
and All(op) | [register[0], register[1]]
are OK, but All(op) | (register[0], register[1])
isn't allowed, and it isn't really clear why it shouldn't be. Finally, I think the block should fail by raising e.g. a ValueError rather than a blank AssertionError.
it would be convenient to allow measurement in an arbitrary basis
at the moment you need to apply some operator that converts from your specific basis to the computational one, then measure, then revert it. it's okay, but not ideal.
I'm having trouble measuring ProjectQ's performance. Something is causing a serious slowdown.
For example, I ran these commands from my terminal:
mkvirtualenv tmp
pip install pybind11
pip install projectq
python speed_test.py
Here are the contents of speed_test.py
:
from __future__ import print_function
import time
from projectq.backends import Simulator
from projectq.cengines import MainEngine
from projectq.ops import X, H, Toffoli
def main():
n = 256
sim = Simulator()
eng = MainEngine(backend=sim, engine_list=[])
qubits = eng.allocate_qureg(3)
for qubit_count in range(4, 20):
qubits.append(eng.allocate_qubit())
t = time.time()
m = len(qubits)
for i in range(n):
a, b, c = qubits[i % m], qubits[(i+1) % m], qubits[(i+2) % m]
Toffoli | (a, b, c)
X | a
H | a
dt = time.time() - t
print("{} gates/sec @ {} qubits".format(int(3*n/dt), len(qubits)))
if __name__ == "__main__":
main()
And got these results:
199 gates/sec @ 4 qubits
192 gates/sec @ 5 qubits
190 gates/sec @ 6 qubits
194 gates/sec @ 7 qubits
183 gates/sec @ 8 qubits
199 gates/sec @ 9 qubits
198 gates/sec @ 10 qubits
200 gates/sec @ 11 qubits
206 gates/sec @ 12 qubits
193 gates/sec @ 13 qubits
195 gates/sec @ 14 qubits
183 gates/sec @ 15 qubits
195 gates/sec @ 16 qubits
190 gates/sec @ 17 qubits
196 gates/sec @ 18 qubits
184 gates/sec @ 19 qubits
Exception RuntimeError: 'Error: Qubit has not been measured / uncomputed! [...]
[...]
Those rates are terrible. I get higher performance with the python simulator up to 14 qubits:
(Note: This is the (slow) Python simulator.)
18598 gates/sec @ 4 qubits
16701 gates/sec @ 5 qubits
13797 gates/sec @ 6 qubits
9966 gates/sec @ 7 qubits
6365 gates/sec @ 8 qubits
3621 gates/sec @ 9 qubits
1983 gates/sec @ 10 qubits
1038 gates/sec @ 11 qubits
532 gates/sec @ 12 qubits
264 gates/sec @ 13 qubits
135 gates/sec @ 14 qubits
68 gates/sec @ 15 qubits
[...]
Last month when I speed-tested projectq, it was getting numbers similar to Quirk: 8000 gates/sec at 16 qubits. I'm not sure what would have changed in the meantime, but performance seems to have dropped by 50x.
I have confirmed in my own debugging that the line self._simulator.apply_controlled_gate
seems to be the big offender, but I haven't figured out much more than that.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.