Skip to content

Single qudit

This simulates the operation of a single qudit, i.e. collective spin or angular momentum operator. It can be used in qiskit-cold-atom as described in this tutorial. Below you can find the API of the simulator.

Config

In this module we define all the configuration parameters for the singlequdit package.

No simulation is performed here. The entire logic is implemented in the spooler.py module.

LoadInstruction

Bases: BaseModel

The load instruction. As each instruction it requires the

Attributes:

Name Type Description
name Literal['load']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the indices should be between 0 and N_MAX_WIRES-1

params Annotated[List[Annotated[int, Field(ge=1, le=N_MAX_ATOMS)]], Field(min_length=1, max_length=1)]

has to be empty

Source code in singlequdit/config.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
class LoadInstruction(BaseModel):
    """
    The load instruction. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the indices should be between 0 and N_MAX_WIRES-1
        params: has to be empty
    """

    name: Literal["load"]
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[
        List[Annotated[int, Field(ge=1, le=N_MAX_ATOMS)]],
        Field(min_length=1, max_length=1),
    ]

LocalSqueezingInstruction

Bases: GateInstruction

The rlz2 instruction. As each instruction it requires the

Attributes:

Name Type Description
name Literal['rlz2']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the indices should be between 0 and N_MAX_WIRES-1

params Annotated[List[Annotated[float, Field(ge=0, le=10 * 2 * pi)]], Field(min_length=1, max_length=1)]

has to be empty

Source code in singlequdit/config.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class LocalSqueezingInstruction(GateInstruction):
    """
    The rlz2 instruction. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the indices should be between 0 and N_MAX_WIRES-1
        params: has to be empty
    """

    name: Literal["rlz2"] = "rlz2"
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[
        List[Annotated[float, Field(ge=0, le=10 * 2 * np.pi)]],
        Field(min_length=1, max_length=1),
    ]

    # a string that is sent over to the config dict and that is necessary for compatibility with QISKIT.
    parameters: str = "chi"
    description: str = "Evolution under lz2"
    # TODO: This should become most likely a type that is then used for the enforcement of the wires.
    coupling_map: List = [[0]]
    qasm_def: str = "gate rlz2(chi) {}"

MeasureBarrierInstruction

Bases: BaseModel

The measure and barrier instruction. As each instruction it requires the

Attributes:

Name Type Description
name Literal['measure', 'barrier']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the index should be 0

params Annotated[List[float], Field(min_length=0, max_length=0)]

has to be empty

Source code in singlequdit/config.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
class MeasureBarrierInstruction(BaseModel):
    """
    The measure and barrier instruction. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the index should be 0
        params: has to be empty
    """

    name: Literal["measure", "barrier"]
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[List[float], Field(min_length=0, max_length=0)]

RlxInstruction

Bases: GateInstruction

The rlx instruction. As each instruction it requires the

Attributes:

Name Type Description
name Literal['rlx']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the indices should be between 0 and N_MAX_WIRES-1

params Annotated[List[Annotated[float, Field(ge=0, le=2 * pi)]], Field(min_length=1, max_length=1)]

has to be empty

Source code in singlequdit/config.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class RlxInstruction(GateInstruction):
    """
    The rlx instruction. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the indices should be between 0 and N_MAX_WIRES-1
        params: has to be empty
    """

    name: Literal["rlx"] = "rlx"
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[
        List[Annotated[float, Field(ge=0, le=2 * np.pi)]],
        Field(min_length=1, max_length=1),
    ]

    # a string that is sent over to the config dict and that is necessary for compatibility with QISKIT.
    parameters: str = "omega"
    description: str = "Evolution under Lx"
    # TODO: This should become most likely a type that is then used for the enforcement of the wires.
    coupling_map: List = [[0]]
    qasm_def: str = "gate lrx(omega) {}"

RlzInstruction

Bases: GateInstruction

The rlz instruction. As each instruction it requires the

Attributes:

Name Type Description
name Literal['rlz']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the indices should be between 0 and N_MAX_WIRES-1

params Annotated[List[Annotated[float, Field(ge=0, le=2 * pi)]], Field(min_length=1, max_length=1)]

has to be empty

Source code in singlequdit/config.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class RlzInstruction(GateInstruction):
    """
    The rlz instruction. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the indices should be between 0 and N_MAX_WIRES-1
        params: has to be empty
    """

    name: Literal["rlz"] = "rlz"
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[
        List[Annotated[float, Field(ge=0, le=2 * np.pi)]],
        Field(min_length=1, max_length=1),
    ]

    # a string that is sent over to the config dict and that is necessary for compatibility with QISKIT.
    parameters: str = "delta"
    description: str = "Evolution under the Z gate"
    # TODO: This should become most likely a type that is then used for the enforcement of the wires.
    coupling_map: List = [[0]]
    qasm_def: str = "gate rlz(delta) {}"

SingleQuditExperiment

Bases: BaseModel

The class that defines the single qudit experiments

Source code in singlequdit/config.py
175
176
177
178
179
180
181
182
183
184
185
186
187
class SingleQuditExperiment(BaseModel):
    """
    The class that defines the single qudit experiments
    """

    wire_order: Literal["interleaved", "sequential"] = "sequential"
    # mypy keeps throwing errors here because it does not understand the type.
    # not sure how to fix it, so we leave it as is for the moment
    # HINT: Annotated does not work
    shots: Annotated[int, Field(gt=0, le=N_MAX_SHOTS)]
    num_wires: Literal[1]
    instructions: List[list]
    seed: Optional[int] = None

SinglequditFullInstruction

Bases: GateInstruction

The evolution under the full Hamiltonian. As each instruction it requires the

Attributes:

Name Type Description
name Literal['sq_full']

The string to identify the instruction

wires Annotated[List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)]

The wire on which the instruction should be applied so the indices should be between 0 and N_MAX_WIRES-1

params Annotated[List[Annotated[float, Field(ge=0, le=5000000.0 * pi)]], Field(min_length=3, max_length=3)]

Define the parameter for RX, RZand RZ2 in this order

Source code in singlequdit/config.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class SinglequditFullInstruction(GateInstruction):
    """
    The evolution under the full Hamiltonian. As each instruction it requires the

    Attributes:
        name: The string to identify the instruction
        wires: The wire on which the instruction should be applied
            so the indices should be between 0 and N_MAX_WIRES-1
        params: Define the parameter for `RX`, `RZ`and `RZ2` in this order
    """

    name: Literal["sq_full"] = "sq_full"
    wires: Annotated[
        List[Annotated[int, Field(ge=0, le=0)]], Field(min_length=0, max_length=1)
    ]
    params: Annotated[
        List[Annotated[float, Field(ge=0, le=5e6 * np.pi)]],
        Field(min_length=3, max_length=3),
    ]

    # a string that is sent over to the config dict and that is necessary for compatibility with QISKIT.
    parameters: str = "omega, delta, chi"
    description: str = "Apply the full time evolution on the array."
    # TODO: This should become most likely a type that is then used for the enforcement of the wires.
    coupling_map: List = [[0]]
    qasm_def: str = "gate sq_full(omega, delta, chi) {}"

Simulation code

The module that contains all the necessary logic to simulate the singlequdit. It has to implement the code that is executed for all the instructions that we defined in the conf.py file.

gen_circuit(exp_name, json_dict)

The function the creates the instructions for the circuit. json_dict: The list of instructions for the specific run.

Source code in singlequdit/spooler.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def gen_circuit(exp_name: str, json_dict: ExperimentalInputDict) -> ExperimentDict:
    """The function the creates the instructions for the circuit.
    json_dict: The list of instructions for the specific run.
    """
    # pylint: disable=R0914
    ins_list = json_dict.instructions
    n_shots = json_dict.shots
    if json_dict.seed is not None:
        np.random.seed(json_dict.seed)

    n_atoms = 1
    spin_len = n_atoms / 2  # spin length

    # let's put together spin matrices
    dim_qudit = n_atoms + 1
    qudit_range = np.arange(spin_len, -(spin_len + 1), -1)

    lx = csc_matrix(
        1
        / 2
        * diags(
            [
                np.sqrt(
                    [(spin_len - m + 1) * (spin_len + m) for m in qudit_range[:-1]]
                ),
                np.sqrt([(spin_len + m + 1) * (spin_len - m) for m in qudit_range[1:]]),
            ],
            [-1, 1],
        )
    )
    lz = csc_matrix(diags([qudit_range], [0]))
    lz2 = lz.multiply(lz)

    psi = 1j * np.zeros(dim_qudit)
    psi[0] = 1 + 1j * 0
    shots_array = []
    # work our way through the instructions
    for inst in ins_list:
        # this must always be the first instruction. Otherwise we should
        # raise some error
        if inst.name == "load":
            n_atoms = int(inst.params[0])
            spin_len = n_atoms / 2
            # length of the qudit
            dim_qudit = n_atoms + 1
            qudit_range = np.arange(spin_len, -(spin_len + 1), -1)

            lx = csc_matrix(
                1
                / 2
                * diags(
                    [
                        np.sqrt(
                            [
                                (spin_len - m + 1) * (spin_len + m)
                                for m in qudit_range[:-1]
                            ]
                        ),
                        np.sqrt(
                            [
                                (spin_len + m + 1) * (spin_len - m)
                                for m in qudit_range[1:]
                            ]
                        ),
                    ],
                    [-1, 1],
                )
            )
            lz = csc_matrix(diags([qudit_range], [0]))

            lz2 = lz.multiply(lz)

            psi = 1j * np.zeros(dim_qudit)
            psi[0] = 1 + 1j * 0

        if inst.name == "rlx":
            theta = inst.params[0]
            psi = expm_multiply(-1j * theta * lx, psi)
        if inst.name == "rlz":
            theta = inst.params[0]
            psi = expm_multiply(-1j * theta * lz, psi)
        if inst.name == "rlz2":
            theta = inst.params[0]
            psi = expm_multiply(-1j * theta * lz2, psi)
        if inst.name == "sq_full":
            omega, delta, chi = inst.params
            h_full = omega * lx + delta * lz + chi * lz2
            psi = expm_multiply(-1j * h_full, psi)
        if inst.name == "measure":
            probs = np.abs(psi) ** 2
            result = np.random.choice(np.arange(dim_qudit), p=probs, size=n_shots)
    shots_array = result.tolist()
    exp_sub_dict = create_memory_data(shots_array, exp_name, n_shots, ins_list)
    return exp_sub_dict

Comments