LevelEncoding
class LevelEncoding
Bases: object
The 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 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 is stored as |
|---|---|---|
UNARY_PROJECTED | the one-hot pattern with qubit set, with a projector on every idle qubit | |
UNARY_UNPROJECTED | the same one-hot pattern, without the projectors | |
STANDARD_BINARY | the binary digits of | |
GRAY | the Gray code |
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 qubits. They differ in the operator built from those patterns. UNARY_UNPROJECTED leaves the idle qubits alone, which is the weight-2 form whose gate count grows only linearly in . UNARY_PROJECTED also puts a projector on each idle qubit, so its terms touch all 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 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 the unary encoding needs roughly three times more two-qubit gates, whereas by 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_BINARY and GRAY emit 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'
[1]
N. P. D. Sawaya et al., Resource-efficient digital quantum simulation of d-level systems for photonic, vibrational, and spin-s Hamiltonians, npj Quantum Information 6, 49 (2020), arXiv:1909.12847.
Attributes
GRAY
Default value: LevelEncoding.GRAY
The binary-reflected Gray code of the level.
It costs the same 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
Default value: LevelEncoding.STANDARD_BINARY
The binary digits of the level.
A system with levels costs qubits. A system with a single level costs none, because its only level needs no qubit to be told apart from any other. When 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 (011 and 100 above differ in all three), which is what makes the operators that connect them higher-weight than under GRAY.
UNARY_PROJECTED
Default value: LevelEncoding.UNARY_PROJECTED
One qubit per level; level sets qubit . Every idle qubit carries a projector.
A system with levels costs qubits, and only of the 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 on each of the other qubits of the system. Its terms therefore touch all qubits, vanish outside the one-hot subspace, and are costlier to synthesize than those of UNARY_UNPROJECTED, by a margin that grows quickly with .
>>> 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 in bit , so qubit 0 is the rightmost digit.
UNARY_UNPROJECTED
Default value: 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 is. Its gate count then grows only linearly in .
>>> 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
from_name
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 name is not a recognized name.
level_to_bits
level_to_bits(level)
Returns the qubit basis state representing level level.
Parameters
level – the level, counted from zero.
Returns
A bitmask whose bit is the value of qubit of the system.
Raises
ValueError – for a unary encoding if level is 128 or more, since the unary encodings hold at most 128 levels.
num_qubits
num_qubits(num_levels)
Returns the number of qubits needed to represent a system with num_levels levels.
Parameters
num_levels – the number of levels of the system.
Returns
for the two unary encodings and for the two compact encodings. The compact encodings can address more levels than whenever it is not a power of two; those surplus levels are never populated by the operators built from the encoding.