예제 #1
0
def get_engine_list(token=None, device=None):
    # Access to the hardware properties via show_devices
    # Can also be extended to take into account gate fidelities, new available
    # gate, etc..
    devices = show_devices(token)
    aqt_setup = []
    if device not in devices:
        raise DeviceOfflineError('Error when configuring engine list: device '
                                 'requested for Backend not connected')
    if device == 'aqt_simulator':
        # The 11 qubit online simulator doesn't need a specific mapping for
        # gates. Can also run wider gateset but this setup keep the
        # restrictedgateset setup for coherence
        mapper = BasicMapperEngine()
        # Note: Manual Mapper doesn't work, because its map is updated only if
        # gates are applied if gates in the register are not used, then it
        # will lead to state errors
        res = dict()
        for i in range(devices[device]['nq']):
            res[i] = i
        mapper.current_mapping = res
        aqt_setup = [mapper]
    else:
        # If there is an online device not handled into ProjectQ it's not too
        # bad, the engine_list can be constructed manually with the
        # appropriate mapper and the 'coupling_map' parameter
        raise DeviceNotHandledError('Device not yet fully handled by ProjectQ')

    # Most gates need to be decomposed into a subset that is manually converted
    # in the backend (until the implementation of the U1,U2,U3)
    setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry),
                                              two_qubit_gates=(Rxx, ),
                                              other_gates=(Barrier, ))
    setup.extend(aqt_setup)
    return setup
예제 #2
0
def test_runtime_error():
    sim = ClassicalSimulator()
    mapper = BasicMapperEngine()
    mapper.current_mapping = {}
    eng = MainEngine(sim, [mapper])
    with pytest.raises(RuntimeError):
        eng.backend.read_bit(WeakQubitRef(None, 1))
예제 #3
0
def test_aqt_backend_functional_test(monkeypatch):
    correct_info = {
        'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], '
        '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], '
        '["Y", 3.5, [1]], ["X", 3.5, [2]]]',
        'nq': 3,
        'shots': 10,
        'backend': {'name': 'simulator'},
    }

    def mock_send(*args, **kwargs):
        assert args[0] == correct_info
        return [0, 6, 0, 6, 0, 0, 0, 6, 0, 6]

    monkeypatch.setattr(_aqt, "send", mock_send)

    backend = _aqt.AQTBackend(verbose=True, num_runs=10)
    # no circuit has been executed -> raises exception
    with pytest.raises(RuntimeError):
        backend.get_probabilities([])

    mapper = BasicMapperEngine()
    res = {}
    for i in range(4):
        res[i] = i
    mapper.current_mapping = res

    eng = MainEngine(backend=backend, engine_list=[mapper])

    unused_qubit = eng.allocate_qubit()
    qureg = eng.allocate_qureg(2)
    # entangle the qureg
    Ry(math.pi / 2) | qureg[0]
    Rx(math.pi / 2) | qureg[0]
    Rx(math.pi / 2) | qureg[0]
    Ry(math.pi / 2) | qureg[0]
    Rxx(math.pi / 2) | (qureg[0], qureg[1])
    Rx(7 * math.pi / 2) | qureg[0]
    Ry(7 * math.pi / 2) | qureg[0]
    Rx(7 * math.pi / 2) | qureg[1]
    All(Barrier) | qureg
    del unused_qubit
    # measure; should be all-0 or all-1
    All(Measure) | qureg
    # run the circuit
    eng.flush()
    prob_dict = eng.backend.get_probabilities([qureg[0], qureg[1]])
    assert prob_dict['11'] == pytest.approx(0.4)
    assert prob_dict['00'] == pytest.approx(0.6)

    # Unknown qubit and no mapper
    invalid_qubit = [WeakQubitRef(eng, 10)]
    with pytest.raises(RuntimeError):
        eng.backend.get_probabilities(invalid_qubit)

    with pytest.raises(RuntimeError):
        eng.backend.get_probabilities(eng.allocate_qubit())
    def __init__(self):
        """
        Initialize an IBM 5-qubit mapper compiler engine.

        Resets the mapping.
        """
        BasicMapperEngine.__init__(self)
        self.current_mapping = dict()
        self._reset()
예제 #5
0
    def __init__(self, map_fun=lambda x: x):
        """
        Initialize the mapper to a given mapping. If no mapping function is
        provided, the qubit id is used as the location.

        Args:
            map_fun (function): Function which, given the qubit id, returns
                an integer describing the physical location (must be constant).
        """
        BasicMapperEngine.__init__(self)
        self.map = map_fun
        self.current_mapping = dict()
예제 #6
0
def test_ibm_sent_error_2(monkeypatch):
    backend = _ibm.IBMBackend(verbose=True)
    mapper = BasicMapperEngine()
    res = dict()
    for i in range(4):
        res[i] = i
    mapper.current_mapping = res
    eng = MainEngine(backend=backend, engine_list=[mapper])
    qubit = eng.allocate_qubit()
    Rx(math.pi) | qubit

    with pytest.raises(Exception):
        S | qubit  # no setup to decompose S gate, so not accepted by the backend
    dummy = DummyEngine()
    dummy.is_last_engine = True
    eng.next_engine = dummy
예제 #7
0
def test_simulator_convert_logical_to_mapped_qubits(sim):
    mapper = BasicMapperEngine()

    def receive(command_list):
        pass

    mapper.receive = receive
    eng = MainEngine(sim, [mapper])
    qubit0 = eng.allocate_qubit()
    qubit1 = eng.allocate_qubit()
    mapper.current_mapping = {
        qubit0[0].id: qubit1[0].id,
        qubit1[0].id: qubit0[0].id
    }
    assert (sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) == qubit1 +
            qubit0)
예제 #8
0
def test_ibm_errors():
    backend = _ibm.IBMBackend(verbose=True, num_runs=1000)
    mapper = BasicMapperEngine()
    mapper.current_mapping = {0: 0}
    eng = MainEngine(backend=backend, engine_list=[mapper])

    qb0 = WeakQubitRef(engine=None, idx=0)

    # No LogicalQubitIDTag
    with pytest.raises(RuntimeError):
        eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],)))

    eng = MainEngine(backend=backend, engine_list=[])

    # No mapper
    with pytest.raises(RuntimeError):
        eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],), tags=(LogicalQubitIDTag(1),)))
예제 #9
0
    def __init__(self, connections=None):
        """
        Initialize an IBM 5-qubit mapper compiler engine.

        Resets the mapping.
        """
        BasicMapperEngine.__init__(self)
        self.current_mapping = dict()
        self._reset()
        self._cmds = []
        self._interactions = dict()

        if connections is None:
            #general connectivity easier for testing functions
            self.connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4),
                                    (2, 1), (2, 3), (2, 4), (3, 1), (3, 4),
                                    (4, 3)])
        else:
            self.connections = connections
예제 #10
0
def test_ibm_sent_error(monkeypatch):
    # patch send
    def mock_send(*args, **kwargs):
        raise TypeError

    monkeypatch.setattr(_ibm, "send", mock_send)
    backend = _ibm.IBMBackend(verbose=True)
    mapper = BasicMapperEngine()
    res = dict()
    for i in range(4):
        res[i] = i
    mapper.current_mapping = res
    eng = MainEngine(backend=backend, engine_list=[mapper])
    qubit = eng.allocate_qubit()
    Rx(math.pi) | qubit
    with pytest.raises(Exception):
        qubit[0].__del__()
        eng.flush()
    # atexit sends another FlushGate, therefore we remove the backend:
    dummy = DummyEngine()
    dummy.is_last_engine = True
    eng.next_engine = dummy
예제 #11
0
    def __init__(self, num_qubits, cyclic=False, storage=1000):
        """
        Initialize a LinearMapper compiler engine.

        Args:
            num_qubits(int): Number of physical qubits in the linear chain
            cyclic(bool): If 1D chain is a cycle. Default is False.
            storage(int): Number of gates to temporarily store, default is 1000
        """
        BasicMapperEngine.__init__(self)
        self.num_qubits = num_qubits
        self.cyclic = cyclic
        self.storage = storage
        # Storing commands
        self._stored_commands = list()
        # Logical qubit ids for which the Allocate gate has already been
        # processed and sent to the next engine but which are not yet
        # deallocated:
        self._currently_allocated_ids = set()
        # Statistics:
        self.num_mappings = 0
        self.depth_of_swaps = dict()
        self.num_of_swaps_per_mapping = dict()
예제 #12
0
def test_main_engine_init_mapper():
    class LinearMapper(BasicMapperEngine):
        pass

    mapper1 = LinearMapper()
    mapper2 = BasicMapperEngine()
    engine_list1 = [mapper1]
    eng1 = _main.MainEngine(engine_list=engine_list1)
    assert eng1.mapper == mapper1
    engine_list2 = [mapper2]
    eng2 = _main.MainEngine(engine_list=engine_list2)
    assert eng2.mapper == mapper2
    engine_list3 = [mapper1, mapper2]
    with pytest.raises(_main.UnsupportedEngineError):
        _main.MainEngine(engine_list=engine_list3)
예제 #13
0
def test_ibm_backend_functional_test(monkeypatch):
    correct_info = {
        'json': [{
            'qubits': [1],
            'name': 'u2',
            'params': [0, 3.141592653589793]
        }, {
            'qubits': [1, 2],
            'name': 'cx'
        }, {
            'qubits': [1, 3],
            'name': 'cx'
        }, {
            'qubits': [1],
            'name': 'u3',
            'params': [6.28318530718, 0, 0]
        }, {
            'qubits': [1],
            'name': 'u1',
            'params': [11.780972450962]
        }, {
            'qubits': [1],
            'name': 'u3',
            'params': [6.28318530718, 0, 0]
        }, {
            'qubits': [1],
            'name': 'u1',
            'params': [10.995574287564]
        }, {
            'qubits': [1, 2, 3],
            'name': 'barrier'
        }, {
            'qubits': [1],
            'name': 'u3',
            'params': [0.2, -1.5707963267948966, 1.5707963267948966]
        }, {
            'qubits': [1],
            'name': 'measure',
            'memory': [1]
        }, {
            'qubits': [2],
            'name': 'measure',
            'memory': [2]
        }, {
            'qubits': [3],
            'name': 'measure',
            'memory': [3]
        }],
        'nq':
        4,
        'shots':
        1000,
        'maxCredits':
        10,
        'backend': {
            'name': 'ibmq_qasm_simulator'
        }
    }

    # {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}}
    def mock_send(*args, **kwargs):
        assert args[0] == correct_info
        return {
            'data': {
                'counts': {
                    '0x0': 504,
                    '0x2': 8,
                    '0xc': 6,
                    '0xe': 482
                }
            },
            'header': {
                'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]],
                'creg_sizes': [['c', 4]],
                'memory_slots':
                4,
                'n_qubits':
                32,
                'name':
                'circuit0',
                'qreg_sizes': [['q', 32]],
                'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3],
                                 ['q', 4], ['q', 5], ['q', 6], ['q', 7],
                                 ['q', 8], ['q', 9], ['q', 10], ['q', 11],
                                 ['q', 12], ['q', 13], ['q', 14], ['q', 15],
                                 ['q', 16], ['q', 17], ['q', 18], ['q', 19],
                                 ['q', 20], ['q', 21], ['q', 22], ['q', 23],
                                 ['q', 24], ['q', 25], ['q', 26], ['q', 27],
                                 ['q', 28], ['q', 29], ['q', 30], ['q', 31]]
            },
            'metadata': {
                'measure_sampling': True,
                'method': 'statevector',
                'parallel_shots': 1,
                'parallel_state_update': 16
            },
            'seed_simulator': 465435780,
            'shots': 1000,
            'status': 'DONE',
            'success': True,
            'time_taken': 0.0045786460000000005
        }

    monkeypatch.setattr(_ibm, "send", mock_send)

    backend = _ibm.IBMBackend(verbose=True, num_runs=1000)
    import sys
    # no circuit has been executed -> raises exception
    with pytest.raises(RuntimeError):
        backend.get_probabilities([])
    mapper = BasicMapperEngine()
    res = dict()
    for i in range(4):
        res[i] = i
    mapper.current_mapping = res
    ibm_setup = [mapper]
    setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H),
                                              two_qubit_gates=(CNOT, ),
                                              other_gates=(Barrier, ))
    setup.extend(ibm_setup)
    eng = MainEngine(backend=backend, engine_list=setup)
    # 4 qubits circuit is run, but first is unused to test ability for
    # get_probability to return the correct values for a subset of the total
    # register
    unused_qubit = eng.allocate_qubit()
    qureg = eng.allocate_qureg(3)
    # entangle the qureg
    Entangle | qureg
    Tdag | qureg[0]
    Sdag | qureg[0]
    Barrier | qureg
    Rx(0.2) | qureg[0]
    del unused_qubit
    # measure; should be all-0 or all-1
    All(Measure) | qureg
    # run the circuit
    eng.flush()
    prob_dict = eng.backend.get_probabilities([qureg[2], qureg[1]])
    assert prob_dict['00'] == pytest.approx(0.512)
    assert prob_dict['11'] == pytest.approx(0.488)
    result = "\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];"
    if sys.version_info.major == 3:
        result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];"
        result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];"
    else:
        result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];"
        result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];"
    result += "\nbarrier q[1], q[2], q[3];"
    result += "\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];"
    result += "\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];"

    assert eng.backend.get_qasm() == result

    with pytest.raises(RuntimeError):
        eng.backend.get_probabilities(eng.allocate_qubit())
예제 #14
0
def test_ibm_retrieve(monkeypatch):
    # patch send
    def mock_retrieve(*args, **kwargs):
        return {
            'data': {
                'counts': {
                    '0x0': 504,
                    '0x2': 8,
                    '0xc': 6,
                    '0xe': 482
                }
            },
            'header': {
                'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]],
                'creg_sizes': [['c', 4]],
                'memory_slots':
                4,
                'n_qubits':
                32,
                'name':
                'circuit0',
                'qreg_sizes': [['q', 32]],
                'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3],
                                 ['q', 4], ['q', 5], ['q', 6], ['q', 7],
                                 ['q', 8], ['q', 9], ['q', 10], ['q', 11],
                                 ['q', 12], ['q', 13], ['q', 14], ['q', 15],
                                 ['q', 16], ['q', 17], ['q', 18], ['q', 19],
                                 ['q', 20], ['q', 21], ['q', 22], ['q', 23],
                                 ['q', 24], ['q', 25], ['q', 26], ['q', 27],
                                 ['q', 28], ['q', 29], ['q', 30], ['q', 31]]
            },
            'metadata': {
                'measure_sampling': True,
                'method': 'statevector',
                'parallel_shots': 1,
                'parallel_state_update': 16
            },
            'seed_simulator': 465435780,
            'shots': 1000,
            'status': 'DONE',
            'success': True,
            'time_taken': 0.0045786460000000005
        }

    monkeypatch.setattr(_ibm, "retrieve", mock_retrieve)
    backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000)
    mapper = BasicMapperEngine()
    res = dict()
    for i in range(4):
        res[i] = i
    mapper.current_mapping = res
    ibm_setup = [mapper]
    setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H),
                                              two_qubit_gates=(CNOT, ))
    setup.extend(ibm_setup)
    eng = MainEngine(backend=backend, engine_list=setup)
    unused_qubit = eng.allocate_qubit()
    qureg = eng.allocate_qureg(3)
    # entangle the qureg
    Entangle | qureg
    Tdag | qureg[0]
    Sdag | qureg[0]
    Barrier | qureg
    Rx(0.2) | qureg[0]
    del unused_qubit
    # measure; should be all-0 or all-1
    All(Measure) | qureg
    # run the circuit
    eng.flush()
    prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]])
    assert prob_dict['000'] == pytest.approx(0.504)
    assert prob_dict['111'] == pytest.approx(0.482)
    assert prob_dict['011'] == pytest.approx(0.006)
예제 #15
0
def get_engine_list(token=None, device=None):
    """Return the default list of compiler engine for the IBM QE platform."""
    # Access to the hardware properties via show_devices
    # Can also be extended to take into account gate fidelities, new available
    # gate, etc..
    devices = show_devices(token)
    ibm_setup = []
    if device not in devices:
        raise DeviceOfflineError('Error when configuring engine list: device requested for Backend not connected')
    if devices[device]['nq'] == 5:
        # The requested device is a 5 qubit processor
        # Obtain the coupling map specific to the device
        coupling_map = devices[device]['coupling_map']
        coupling_map = list2set(coupling_map)
        mapper = IBM5QubitMapper(coupling_map)
        ibm_setup = [mapper, SwapAndCNOTFlipper(coupling_map), LocalOptimizer(10)]
    elif device == 'ibmq_qasm_simulator':
        # The 32 qubit online simulator doesn't need a specific mapping for
        # gates. Can also run wider gateset but this setup keep the
        # restrictedgateset setup for coherence
        mapper = BasicMapperEngine()
        # Note: Manual Mapper doesn't work, because its map is updated only if
        # gates are applied if gates in the register are not used, then it
        # will lead to state errors
        res = {}
        for i in range(devices[device]['nq']):
            res[i] = i
        mapper.current_mapping = res
        ibm_setup = [mapper]
    elif device == 'ibmq_16_melbourne':
        # Only 15 qubits available on this ibmqx2 unit(in particular qubit 7
        # on the grid), therefore need custom grid mapping
        grid_to_physical = {
            0: 0,
            1: 1,
            2: 2,
            3: 3,
            4: 4,
            5: 5,
            6: 6,
            7: 15,
            8: 14,
            9: 13,
            10: 12,
            11: 11,
            12: 10,
            13: 9,
            14: 8,
            15: 7,
        }
        coupling_map = devices[device]['coupling_map']
        coupling_map = list2set(coupling_map)
        ibm_setup = [
            GridMapper(2, 8, grid_to_physical),
            LocalOptimizer(5),
            SwapAndCNOTFlipper(coupling_map),
            LocalOptimizer(5),
        ]
    else:
        # If there is an online device not handled into ProjectQ it's not too
        # bad, the engine_list can be constructed manually with the
        # appropriate mapper and the 'coupling_map' parameter
        raise DeviceNotHandledError('Device not yet fully handled by ProjectQ')

    # Most IBM devices accept U1,U2,U3,CX gates.
    # Most gates need to be decomposed into a subset that is manually converted
    # in the backend (until the implementation of the U1,U2,U3)
    # available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H
    setup = restrictedgateset.get_engine_list(
        one_qubit_gates=(Rx, Ry, Rz, H), two_qubit_gates=(CNOT,), other_gates=(Barrier,)
    )
    setup.extend(ibm_setup)
    return setup
예제 #16
0
    def __init__(self,
                 num_rows,
                 num_columns,
                 mapped_ids_to_backend_ids=None,
                 storage=1000,
                 optimization_function=lambda x: return_swap_depth(x),
                 num_optimization_steps=50):
        """
        Initialize a GridMapper compiler engine.

        Args:
            num_rows(int): Number of rows in the grid
            num_columns(int): Number of columns in the grid.
            mapped_ids_to_backend_ids(dict): Stores a mapping from mapped ids
                                             which are 0,...,self.num_qubits-1
                                             in row-major order on the grid to
                                             the corresponding qubit ids of the
                                             backend. Key: mapped id. Value:
                                             corresponding backend id.
                                             Default is None which means
                                             backend ids are identical to
                                             mapped ids.
            storage: Number of gates to temporarily store
            optimization_function: Function which takes a list of swaps and
                                   returns a cost value. Mapper chooses a
                                   permutation which minimizes this cost.
                                   Default optimizes for circuit depth.
            num_optimization_steps(int): Number of different permutations to
                                         of the matching to try and minimize
                                         the cost.
        Raises:
            RuntimeError: if incorrect `mapped_ids_to_backend_ids` parameter
        """
        BasicMapperEngine.__init__(self)
        self.num_rows = num_rows
        self.num_columns = num_columns
        self.num_qubits = num_rows * num_columns
        # Internally we use the mapped ids until sending a command.
        # Before sending we use this map to translate to backend ids:
        self._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids
        if self._mapped_ids_to_backend_ids is None:
            self._mapped_ids_to_backend_ids = dict()
            for i in range(self.num_qubits):
                self._mapped_ids_to_backend_ids[i] = i
        if (not (set(self._mapped_ids_to_backend_ids.keys()) == set(
                list(range(self.num_qubits))))
                or not (len(set(self._mapped_ids_to_backend_ids.values()))
                        == self.num_qubits)):
            raise RuntimeError("Incorrect mapped_ids_to_backend_ids parameter")
        self._backend_ids_to_mapped_ids = dict()
        for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items():
            self._backend_ids_to_mapped_ids[backend_id] = mapped_id
        # As we use internally the mapped ids which are in row-major order,
        # we have an internal current mapping which maps from logical ids to
        # these mapped ids:
        self._current_row_major_mapping = deepcopy(self.current_mapping)
        self.storage = storage
        self.optimization_function = optimization_function
        self.num_optimization_steps = num_optimization_steps
        # Randomness to pick permutations if there are too many.
        # This creates an own instance of Random in order to not influence
        # the bound methods of the random module which might be used in other
        # places.
        self._rng = random.Random(11)
        # Storing commands
        self._stored_commands = list()
        # Logical qubit ids for which the Allocate gate has already been
        # processed and sent to the next engine but which are not yet
        # deallocated:
        self._currently_allocated_ids = set()
        # Change between 2D and 1D mappings (2D is a snake like 1D chain)
        # Note it translates to our mapped ids in row major order and not
        # backend ids which might be different.
        self._map_2d_to_1d = dict()
        self._map_1d_to_2d = dict()
        for row_index in range(self.num_rows):
            for column_index in range(self.num_columns):
                if row_index % 2 == 0:
                    mapped_id = row_index * self.num_columns + column_index
                    self._map_2d_to_1d[mapped_id] = mapped_id
                    self._map_1d_to_2d[mapped_id] = mapped_id
                else:
                    mapped_id_2d = row_index * self.num_columns + column_index
                    mapped_id_1d = ((row_index + 1) * self.num_columns -
                                    column_index - 1)
                    self._map_2d_to_1d[mapped_id_2d] = mapped_id_1d
                    self._map_1d_to_2d[mapped_id_1d] = mapped_id_2d
        # Statistics:
        self.num_mappings = 0
        self.depth_of_swaps = dict()
        self.num_of_swaps_per_mapping = dict()