123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- """Primitive circuit operations on quantum circuits."""
- from functools import reduce
- from sympy.core.sorting import default_sort_key
- from sympy.core.containers import Tuple
- from sympy.core.mul import Mul
- from sympy.core.symbol import Symbol
- from sympy.core.sympify import sympify
- from sympy.utilities import numbered_symbols
- from sympy.physics.quantum.gate import Gate
- __all__ = [
- 'kmp_table',
- 'find_subcircuit',
- 'replace_subcircuit',
- 'convert_to_symbolic_indices',
- 'convert_to_real_indices',
- 'random_reduce',
- 'random_insert'
- ]
- def kmp_table(word):
- """Build the 'partial match' table of the Knuth-Morris-Pratt algorithm.
- Note: This is applicable to strings or
- quantum circuits represented as tuples.
- """
- # Current position in subcircuit
- pos = 2
- # Beginning position of candidate substring that
- # may reappear later in word
- cnd = 0
- # The 'partial match' table that helps one determine
- # the next location to start substring search
- table = list()
- table.append(-1)
- table.append(0)
- while pos < len(word):
- if word[pos - 1] == word[cnd]:
- cnd = cnd + 1
- table.append(cnd)
- pos = pos + 1
- elif cnd > 0:
- cnd = table[cnd]
- else:
- table.append(0)
- pos = pos + 1
- return table
- def find_subcircuit(circuit, subcircuit, start=0, end=0):
- """Finds the subcircuit in circuit, if it exists.
- Explanation
- ===========
- If the subcircuit exists, the index of the start of
- the subcircuit in circuit is returned; otherwise,
- -1 is returned. The algorithm that is implemented
- is the Knuth-Morris-Pratt algorithm.
- Parameters
- ==========
- circuit : tuple, Gate or Mul
- A tuple of Gates or Mul representing a quantum circuit
- subcircuit : tuple, Gate or Mul
- A tuple of Gates or Mul to find in circuit
- start : int
- The location to start looking for subcircuit.
- If start is the same or past end, -1 is returned.
- end : int
- The last place to look for a subcircuit. If end
- is less than 1 (one), then the length of circuit
- is taken to be end.
- Examples
- ========
- Find the first instance of a subcircuit:
- >>> from sympy.physics.quantum.circuitutils import find_subcircuit
- >>> from sympy.physics.quantum.gate import X, Y, Z, H
- >>> circuit = X(0)*Z(0)*Y(0)*H(0)
- >>> subcircuit = Z(0)*Y(0)
- >>> find_subcircuit(circuit, subcircuit)
- 1
- Find the first instance starting at a specific position:
- >>> find_subcircuit(circuit, subcircuit, start=1)
- 1
- >>> find_subcircuit(circuit, subcircuit, start=2)
- -1
- >>> circuit = circuit*subcircuit
- >>> find_subcircuit(circuit, subcircuit, start=2)
- 4
- Find the subcircuit within some interval:
- >>> find_subcircuit(circuit, subcircuit, start=2, end=2)
- -1
- """
- if isinstance(circuit, Mul):
- circuit = circuit.args
- if isinstance(subcircuit, Mul):
- subcircuit = subcircuit.args
- if len(subcircuit) == 0 or len(subcircuit) > len(circuit):
- return -1
- if end < 1:
- end = len(circuit)
- # Location in circuit
- pos = start
- # Location in the subcircuit
- index = 0
- # 'Partial match' table
- table = kmp_table(subcircuit)
- while (pos + index) < end:
- if subcircuit[index] == circuit[pos + index]:
- index = index + 1
- else:
- pos = pos + index - table[index]
- index = table[index] if table[index] > -1 else 0
- if index == len(subcircuit):
- return pos
- return -1
- def replace_subcircuit(circuit, subcircuit, replace=None, pos=0):
- """Replaces a subcircuit with another subcircuit in circuit,
- if it exists.
- Explanation
- ===========
- If multiple instances of subcircuit exists, the first instance is
- replaced. The position to being searching from (if different from
- 0) may be optionally given. If subcircuit cannot be found, circuit
- is returned.
- Parameters
- ==========
- circuit : tuple, Gate or Mul
- A quantum circuit.
- subcircuit : tuple, Gate or Mul
- The circuit to be replaced.
- replace : tuple, Gate or Mul
- The replacement circuit.
- pos : int
- The location to start search and replace
- subcircuit, if it exists. This may be used
- if it is known beforehand that multiple
- instances exist, and it is desirable to
- replace a specific instance. If a negative number
- is given, pos will be defaulted to 0.
- Examples
- ========
- Find and remove the subcircuit:
- >>> from sympy.physics.quantum.circuitutils import replace_subcircuit
- >>> from sympy.physics.quantum.gate import X, Y, Z, H
- >>> circuit = X(0)*Z(0)*Y(0)*H(0)*X(0)*H(0)*Y(0)
- >>> subcircuit = Z(0)*Y(0)
- >>> replace_subcircuit(circuit, subcircuit)
- (X(0), H(0), X(0), H(0), Y(0))
- Remove the subcircuit given a starting search point:
- >>> replace_subcircuit(circuit, subcircuit, pos=1)
- (X(0), H(0), X(0), H(0), Y(0))
- >>> replace_subcircuit(circuit, subcircuit, pos=2)
- (X(0), Z(0), Y(0), H(0), X(0), H(0), Y(0))
- Replace the subcircuit:
- >>> replacement = H(0)*Z(0)
- >>> replace_subcircuit(circuit, subcircuit, replace=replacement)
- (X(0), H(0), Z(0), H(0), X(0), H(0), Y(0))
- """
- if pos < 0:
- pos = 0
- if isinstance(circuit, Mul):
- circuit = circuit.args
- if isinstance(subcircuit, Mul):
- subcircuit = subcircuit.args
- if isinstance(replace, Mul):
- replace = replace.args
- elif replace is None:
- replace = ()
- # Look for the subcircuit starting at pos
- loc = find_subcircuit(circuit, subcircuit, start=pos)
- # If subcircuit was found
- if loc > -1:
- # Get the gates to the left of subcircuit
- left = circuit[0:loc]
- # Get the gates to the right of subcircuit
- right = circuit[loc + len(subcircuit):len(circuit)]
- # Recombine the left and right side gates into a circuit
- circuit = left + replace + right
- return circuit
- def _sympify_qubit_map(mapping):
- new_map = {}
- for key in mapping:
- new_map[key] = sympify(mapping[key])
- return new_map
- def convert_to_symbolic_indices(seq, start=None, gen=None, qubit_map=None):
- """Returns the circuit with symbolic indices and the
- dictionary mapping symbolic indices to real indices.
- The mapping is 1 to 1 and onto (bijective).
- Parameters
- ==========
- seq : tuple, Gate/Integer/tuple or Mul
- A tuple of Gate, Integer, or tuple objects, or a Mul
- start : Symbol
- An optional starting symbolic index
- gen : object
- An optional numbered symbol generator
- qubit_map : dict
- An existing mapping of symbolic indices to real indices
- All symbolic indices have the format 'i#', where # is
- some number >= 0.
- """
- if isinstance(seq, Mul):
- seq = seq.args
- # A numbered symbol generator
- index_gen = numbered_symbols(prefix='i', start=-1)
- cur_ndx = next(index_gen)
- # keys are symbolic indices; values are real indices
- ndx_map = {}
- def create_inverse_map(symb_to_real_map):
- rev_items = lambda item: tuple([item[1], item[0]])
- return dict(map(rev_items, symb_to_real_map.items()))
- if start is not None:
- if not isinstance(start, Symbol):
- msg = 'Expected Symbol for starting index, got %r.' % start
- raise TypeError(msg)
- cur_ndx = start
- if gen is not None:
- if not isinstance(gen, numbered_symbols().__class__):
- msg = 'Expected a generator, got %r.' % gen
- raise TypeError(msg)
- index_gen = gen
- if qubit_map is not None:
- if not isinstance(qubit_map, dict):
- msg = ('Expected dict for existing map, got ' +
- '%r.' % qubit_map)
- raise TypeError(msg)
- ndx_map = qubit_map
- ndx_map = _sympify_qubit_map(ndx_map)
- # keys are real indices; keys are symbolic indices
- inv_map = create_inverse_map(ndx_map)
- sym_seq = ()
- for item in seq:
- # Nested items, so recurse
- if isinstance(item, Gate):
- result = convert_to_symbolic_indices(item.args,
- qubit_map=ndx_map,
- start=cur_ndx,
- gen=index_gen)
- sym_item, new_map, cur_ndx, index_gen = result
- ndx_map.update(new_map)
- inv_map = create_inverse_map(ndx_map)
- elif isinstance(item, (tuple, Tuple)):
- result = convert_to_symbolic_indices(item,
- qubit_map=ndx_map,
- start=cur_ndx,
- gen=index_gen)
- sym_item, new_map, cur_ndx, index_gen = result
- ndx_map.update(new_map)
- inv_map = create_inverse_map(ndx_map)
- elif item in inv_map:
- sym_item = inv_map[item]
- else:
- cur_ndx = next(gen)
- ndx_map[cur_ndx] = item
- inv_map[item] = cur_ndx
- sym_item = cur_ndx
- if isinstance(item, Gate):
- sym_item = item.__class__(*sym_item)
- sym_seq = sym_seq + (sym_item,)
- return sym_seq, ndx_map, cur_ndx, index_gen
- def convert_to_real_indices(seq, qubit_map):
- """Returns the circuit with real indices.
- Parameters
- ==========
- seq : tuple, Gate/Integer/tuple or Mul
- A tuple of Gate, Integer, or tuple objects or a Mul
- qubit_map : dict
- A dictionary mapping symbolic indices to real indices.
- Examples
- ========
- Change the symbolic indices to real integers:
- >>> from sympy import symbols
- >>> from sympy.physics.quantum.circuitutils import convert_to_real_indices
- >>> from sympy.physics.quantum.gate import X, Y, H
- >>> i0, i1 = symbols('i:2')
- >>> index_map = {i0 : 0, i1 : 1}
- >>> convert_to_real_indices(X(i0)*Y(i1)*H(i0)*X(i1), index_map)
- (X(0), Y(1), H(0), X(1))
- """
- if isinstance(seq, Mul):
- seq = seq.args
- if not isinstance(qubit_map, dict):
- msg = 'Expected dict for qubit_map, got %r.' % qubit_map
- raise TypeError(msg)
- qubit_map = _sympify_qubit_map(qubit_map)
- real_seq = ()
- for item in seq:
- # Nested items, so recurse
- if isinstance(item, Gate):
- real_item = convert_to_real_indices(item.args, qubit_map)
- elif isinstance(item, (tuple, Tuple)):
- real_item = convert_to_real_indices(item, qubit_map)
- else:
- real_item = qubit_map[item]
- if isinstance(item, Gate):
- real_item = item.__class__(*real_item)
- real_seq = real_seq + (real_item,)
- return real_seq
- def random_reduce(circuit, gate_ids, seed=None):
- """Shorten the length of a quantum circuit.
- Explanation
- ===========
- random_reduce looks for circuit identities in circuit, randomly chooses
- one to remove, and returns a shorter yet equivalent circuit. If no
- identities are found, the same circuit is returned.
- Parameters
- ==========
- circuit : Gate tuple of Mul
- A tuple of Gates representing a quantum circuit
- gate_ids : list, GateIdentity
- List of gate identities to find in circuit
- seed : int or list
- seed used for _randrange; to override the random selection, provide a
- list of integers: the elements of gate_ids will be tested in the order
- given by the list
- """
- from sympy.core.random import _randrange
- if not gate_ids:
- return circuit
- if isinstance(circuit, Mul):
- circuit = circuit.args
- ids = flatten_ids(gate_ids)
- # Create the random integer generator with the seed
- randrange = _randrange(seed)
- # Look for an identity in the circuit
- while ids:
- i = randrange(len(ids))
- id = ids.pop(i)
- if find_subcircuit(circuit, id) != -1:
- break
- else:
- # no identity was found
- return circuit
- # return circuit with the identity removed
- return replace_subcircuit(circuit, id)
- def random_insert(circuit, choices, seed=None):
- """Insert a circuit into another quantum circuit.
- Explanation
- ===========
- random_insert randomly chooses a location in the circuit to insert
- a randomly selected circuit from amongst the given choices.
- Parameters
- ==========
- circuit : Gate tuple or Mul
- A tuple or Mul of Gates representing a quantum circuit
- choices : list
- Set of circuit choices
- seed : int or list
- seed used for _randrange; to override the random selections, give
- a list two integers, [i, j] where i is the circuit location where
- choice[j] will be inserted.
- Notes
- =====
- Indices for insertion should be [0, n] if n is the length of the
- circuit.
- """
- from sympy.core.random import _randrange
- if not choices:
- return circuit
- if isinstance(circuit, Mul):
- circuit = circuit.args
- # get the location in the circuit and the element to insert from choices
- randrange = _randrange(seed)
- loc = randrange(len(circuit) + 1)
- choice = choices[randrange(len(choices))]
- circuit = list(circuit)
- circuit[loc: loc] = choice
- return tuple(circuit)
- # Flatten the GateIdentity objects (with gate rules) into one single list
- def flatten_ids(ids):
- collapse = lambda acc, an_id: acc + sorted(an_id.equivalent_ids,
- key=default_sort_key)
- ids = reduce(collapse, ids, [])
- ids.sort(key=default_sort_key)
- return ids
|