LevelEncoding¶
- class LevelEncoding¶
Bases:
objectThe layout of the levels of a single system on qubits.
A level encoding decides which computational basis state of a block of qubits represents each of the \(d\) levels of one system. It is independent of what the levels are, so mappers in different bases (for example
occupation_encoding(), whose levels are boson occupation numbers) share the same choice.Encoding
Qubits per system
Level \(n\) is stored as
\(d\)
the one-hot pattern with qubit \(n\) set, with a projector on every idle qubit
\(d\)
the same one-hot pattern, without the projectors
\(\lceil \log_2 d \rceil\)
the binary digits of \(n\)
\(\lceil \log_2 d \rceil\)
the Gray code \(n \oplus (n \gg 1)\)
There is no universally best choice, so all of them are offered on an equal footing. [1] The two unary encodings share their bit patterns and give ladder terms with only two non-diagonal factors, at a cost of \(d\) qubits. They differ in the operator built from those patterns.
UNARY_UNPROJECTEDleaves the idle qubits alone, which is the weight-2 form whose gate count grows only linearly in \(d\).UNARY_PROJECTEDalso puts a projector \(\lvert 0 \rangle\langle 0 \rvert\) on each idle qubit, so its terms touch all \(d\) qubits and are costlier to synthesize. The two are equal on the one-hot subspace, and the unprojected operator is undefined outside it, so the choice is a modeling decision and not a pure optimization. The two compact encodings need only \(\lceil \log_2 d \rceil\) qubits but produce terms with more non-diagonal factors, and their projectors cannot be dropped, because there the zero bits are part of the level number.Sawaya et al. find that the compact codes are frequently better in both qubit and gate count – at \(d = 4\) the unary encoding needs roughly three times more two-qubit gates, whereas by \(d = 10\) it needs about half. They further find the Gray code preferable for tridiagonal (hopping) terms and standard binary for diagonal (number-operator) terms.
Note that
STANDARD_BINARYandGRAYemit the same number of terms; the Gray code’s advantage lies entirely in the weight distribution, since consecutive levels differ in exactly one bit.Every function that takes a level encoding also accepts its string name, which is what
str()returns:"unary_projected","unary_unprojected","standard_binary"or"gray".from_name()is the inverse, and rejects any other name, including the former"unary":>>> from qiskit_bosons.mappers import LevelEncoding >>> str(LevelEncoding.STANDARD_BINARY) 'standard_binary' >>> LevelEncoding.from_name("gray") LevelEncoding.GRAY >>> LevelEncoding.from_name(str(LevelEncoding.UNARY_UNPROJECTED)) == LevelEncoding.UNARY_UNPROJECTED True >>> LevelEncoding.from_name("unary") Traceback (most recent call last): ... ValueError: unknown encoding "unary"; expected one of 'unary_projected', 'unary_unprojected', 'standard_binary', 'gray'
Attributes
- GRAY = LevelEncoding.GRAY¶
The binary-reflected Gray code of the level.
It costs the same \(\lceil \log_2 d \rceil\) qubits as
STANDARD_BINARY, and the same zero qubits for a single level, but consecutive levels differ in exactly one bit.>>> from qiskit_bosons.mappers import LevelEncoding >>> [LevelEncoding.GRAY.num_qubits(d) for d in (1, 2, 3, 4, 5, 9)] [0, 1, 2, 2, 3, 4] >>> [format(LevelEncoding.GRAY.level_to_bits(n), "03b") for n in range(5)] ['000', '001', '011', '010', '110']
Each code in the list above differs from its neighbor in a single digit, which keeps the operators that connect adjacent levels low-weight.
- STANDARD_BINARY = LevelEncoding.STANDARD_BINARY¶
The binary digits of the level.
A system with \(d\) levels costs \(\lceil \log_2 d \rceil\) qubits. A system with a single level costs none, because its only level needs no qubit to be told apart from any other. When \(d\) is not a power of two, the surplus basis states are never populated.
>>> from qiskit_bosons.mappers import LevelEncoding >>> [LevelEncoding.STANDARD_BINARY.num_qubits(d) for d in (1, 2, 3, 4, 5, 9)] [0, 1, 2, 2, 3, 4] >>> [format(LevelEncoding.STANDARD_BINARY.level_to_bits(n), "03b") for n in range(5)] ['000', '001', '010', '011', '100']
Adjacent levels can differ in many bits (
011and100above differ in all three), which is what makes the operators that connect them higher-weight than underGRAY.
- UNARY_PROJECTED = LevelEncoding.UNARY_PROJECTED¶
One qubit per level; level \(n\) sets qubit \(n\). Every idle qubit carries a projector.
A system with \(d\) levels costs \(d\) qubits, and only \(d\) of the \(2^d\) basis states of those qubits are ever populated. An operator that moves the level up or down by one has two non-diagonal factors, and this variant also puts a projector \(\lvert 0 \rangle\langle 0 \rvert\) on each of the other qubits of the system. Its terms therefore touch all \(d\) qubits, vanish outside the one-hot subspace, and are costlier to synthesize than those of
UNARY_UNPROJECTED, by a margin that grows quickly with \(d\).>>> from qiskit_bosons.mappers import LevelEncoding >>> [LevelEncoding.UNARY_PROJECTED.num_qubits(d) for d in (1, 2, 3, 4, 5, 9)] [1, 2, 3, 4, 5, 9] >>> [format(LevelEncoding.UNARY_PROJECTED.level_to_bits(n), "05b") for n in range(5)] ['00001', '00010', '00100', '01000', '10000']
The bitmask of a level has qubit \(q\) in bit \(q\), so qubit 0 is the rightmost digit.
- UNARY_UNPROJECTED = LevelEncoding.UNARY_UNPROJECTED¶
One qubit per level, as for
UNARY_PROJECTED, but without the projectors on the idle qubits.The bit patterns, the qubit count and the matrix elements are those of
UNARY_PROJECTED. Only the projectors are left out, so an operator that moves the level up or down by one keeps the weight-2 form with two non-diagonal factors and acts as the identity on every other qubit, however large \(d\) is. Its gate count then grows only linearly in \(d\).>>> from qiskit_bosons.mappers import LevelEncoding >>> [LevelEncoding.UNARY_UNPROJECTED.num_qubits(d) for d in (1, 2, 3, 4, 5, 9)] [1, 2, 3, 4, 5, 9] >>> [format(LevelEncoding.UNARY_UNPROJECTED.level_to_bits(n), "05b") for n in range(5)] ['00001', '00010', '00100', '01000', '10000']
The two unary variants are equal on the one-hot subspace. This one is undefined outside it, since nothing suppresses the evolution of an unphysical state, so choosing it is a modeling decision and not a pure optimization. It is only meaningful for the unary code.
Methods
- static from_name(name)¶
Returns the encoding with the given string name.
- Parameters:
name – one of
"unary_projected","unary_unprojected","standard_binary"or"gray".- Raises:
ValueError – if
nameis not a recognized name.
- level_to_bits(level)¶
Returns the qubit basis state representing level
level.- Parameters:
level – the level, counted from zero.
- Returns:
A bitmask whose bit \(q\) is the value of qubit \(q\) of the system.
- Raises:
ValueError – for a unary encoding if
levelis 128 or more, since the unary encodings hold at most 128 levels.
- num_qubits(num_levels)¶
Returns the number of qubits needed to represent a system with
num_levelslevels.- Parameters:
num_levels – the number of levels \(d\) of the system.
- Returns:
\(d\) for the two unary encodings and \(\lceil \log_2 d \rceil\) for the two compact encodings. The compact encodings can address more levels than \(d\) whenever it is not a power of two; those surplus levels are never populated by the operators built from the encoding.