Skip to content

Commit

Permalink
Add checks to avoid too many compiler engines in the engine list (#405)
Browse files Browse the repository at this point in the history
* Add checks to avoid too many compiler engines in the engine list

* Update CHANGELOG

* Apply suggestions from Andreas
  • Loading branch information
Takishima committed Jun 25, 2021
1 parent a0a41fe commit f0a0c1e
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### Deprecated
### Fixed

- Prevent infinite recursion errors when too many compiler engines are added to the MainEngine

### Removed
### Repository

Expand Down
15 changes: 13 additions & 2 deletions projectq/cengines/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def receive(self, command_list): # pylint: disable=unused-argument
"""No-op"""


_N_ENGINES_THRESHOLD = 100


class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes
"""
The MainEngine class provides all functionality of the main compiler engine.
Expand All @@ -61,10 +64,13 @@ class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes
dirty_qubits (Set): Containing all dirty qubit ids
backend (BasicEngine): Access the back-end.
mapper (BasicMapperEngine): Access to the mapper if there is one.
n_engines (int): Current number of compiler engines in the engine list
n_engines_max (int): Maximum number of compiler engines allowed in the engine list. Defaults to 100.
"""

def __init__(self, backend=None, engine_list=None, verbose=False):
def __init__( # pylint: disable=too-many-statements,too-many-branches
self, backend=None, engine_list=None, verbose=False
):
"""
Initialize the main compiler engine and all compiler engines.
Expand Down Expand Up @@ -118,6 +124,7 @@ def __init__(self, backend=None, engine_list=None, verbose=False):
self.dirty_qubits = set()
self.verbose = verbose
self.main_engine = self
self.n_engines_max = _N_ENGINES_THRESHOLD

if backend is None:
backend = Simulator()
Expand Down Expand Up @@ -174,6 +181,10 @@ def __init__(self, backend=None, engine_list=None, verbose=False):
" twice.\n"
)

self.n_engines = len(engine_list)
if self.n_engines > self.n_engines_max:
raise ValueError('Too many compiler engines added to the MainEngine!')

self._qubit_idx = int(0)
for i in range(len(engine_list) - 1):
engine_list[i].next_engine = engine_list[i + 1]
Expand Down
12 changes: 12 additions & 0 deletions projectq/cengines/_main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ def test_main_engine_init_defaults():
assert type(engine) == type(expected)


def test_main_engine_too_many_compiler_engines():
old = _main._N_ENGINES_THRESHOLD
_main._N_ENGINES_THRESHOLD = 3

_main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine()])

with pytest.raises(ValueError):
_main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine(), DummyEngine()])

_main._N_ENGINES_THRESHOLD = old


def test_main_engine_init_mapper():
class LinearMapper(BasicMapperEngine):
pass
Expand Down
8 changes: 8 additions & 0 deletions projectq/meta/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def insert_engine(prev_engine, engine_to_insert):
engine_to_insert (projectq.cengines.BasicEngine):
The engine to insert at the insertion point.
"""
if prev_engine.main_engine is not None:
prev_engine.main_engine.n_engines += 1

if prev_engine.main_engine.n_engines > prev_engine.main_engine.n_engines_max:
raise RuntimeError('Too many compiler engines added to the MainEngine!')

engine_to_insert.main_engine = prev_engine.main_engine
engine_to_insert.next_engine = prev_engine.next_engine
prev_engine.next_engine = engine_to_insert
Expand All @@ -47,6 +53,8 @@ def drop_engine_after(prev_engine):
"""
dropped_engine = prev_engine.next_engine
prev_engine.next_engine = dropped_engine.next_engine
if prev_engine.main_engine is not None:
prev_engine.main_engine.n_engines -= 1
dropped_engine.next_engine = None
dropped_engine.main_engine = None
return dropped_engine
26 changes: 23 additions & 3 deletions projectq/meta/_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from projectq import MainEngine
from projectq.cengines import DummyEngine
from projectq.meta import insert_engine, drop_engine_after


from . import _util


def test_insert_then_drop():
Expand All @@ -30,19 +34,35 @@ def test_insert_then_drop():
assert d1.main_engine is eng
assert d2.main_engine is None
assert d3.main_engine is eng
assert eng.n_engines == 2

insert_engine(d1, d2)
_util.insert_engine(d1, d2)
assert d1.next_engine is d2
assert d2.next_engine is d3
assert d3.next_engine is None
assert d1.main_engine is eng
assert d2.main_engine is eng
assert d3.main_engine is eng
assert eng.n_engines == 3

drop_engine_after(d1)
_util.drop_engine_after(d1)
assert d1.next_engine is d3
assert d2.next_engine is None
assert d3.next_engine is None
assert d1.main_engine is eng
assert d2.main_engine is None
assert d3.main_engine is eng
assert eng.n_engines == 2


def test_too_many_engines():
N = 10

eng = MainEngine(backend=DummyEngine(), engine_list=[])
eng.n_engines_max = N

for _ in range(N - 1):
_util.insert_engine(eng, DummyEngine())

with pytest.raises(RuntimeError):
_util.insert_engine(eng, DummyEngine())

0 comments on commit f0a0c1e

Please sign in to comment.