Coverage for jaxquantum/circuits/circuits.py: 85%
150 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-01 06:26 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-01 06:26 +0000
1"""Circuits.
3Inspired by a mix of Cirq and Qiskit circuits.
4"""
6from flax import struct
7from jax import config
8from typing import List, Optional, Union
9from copy import deepcopy
10from numpy import argsort
11import jax.numpy as jnp
13from jaxquantum.core.operators import identity
14from jaxquantum.circuits.gates import Gate
15from jaxquantum.circuits.constants import SimulateMode
16from jaxquantum.core.qarray import Qarray, concatenate
19config.update("jax_enable_x64", True)
22@struct.dataclass
23class Register:
24 dims: List[int] = struct.field(pytree_node=False)
26 @classmethod
27 def create(cls, dims: List[int]):
28 return Register(dims=dims)
30 def __eq__(self, other):
31 if isinstance(other, Register):
32 return self.dims == other.dims
33 return False
36@struct.dataclass
37class Operation:
38 gate: Gate
39 indices: List[int] = struct.field(pytree_node=False)
40 register: Register
42 @classmethod
43 def create(cls, gate: Gate, indices: Union[int, List[int]], register: Register):
44 if isinstance(indices, int):
45 indices = [indices]
47 assert gate.num_modes == len(indices), (
48 "Number of indices must match gate's num_modes."
49 )
50 assert gate.dims == [register.dims[ind] for ind in indices], (
51 "Indices must match register dimensions."
52 )
54 if any(
55 (0 > ind and ind >= len(register.dims)) or not isinstance(ind, int)
56 for ind in indices
57 ):
58 raise ValueError("Indices must be integers within the register.")
60 return Operation(gate=gate, indices=indices, register=register)
63 def promote(self, op: Qarray) -> Qarray:
64 indices_order = self.indices
65 missing_indices = [
66 i for i in range(len(self.register.dims)) if i not in indices_order
67 ]
68 for j in missing_indices:
69 op = op ^ identity(self.register.dims[j])
70 combined_indices = indices_order + missing_indices
71 sorted_ind = list(argsort(combined_indices))
72 op = op.transpose(sorted_ind)
73 return op
76@struct.dataclass
77class Layer:
78 operations: List[Operation] = struct.field(pytree_node=False)
79 _unique_indices: List[int] = struct.field(pytree_node=False)
80 _default_simulate_mode: SimulateMode = struct.field(pytree_node=False)
82 @classmethod
83 def create(
84 cls, operations: List[Operation], default_simulate_mode=SimulateMode.UNITARY
85 ):
86 all_indices = [ind for op in operations for ind in op.indices]
87 unique_indices = list(set(all_indices))
89 if (
90 default_simulate_mode != SimulateMode.HAMILTONIAN
91 and len(all_indices) != len(unique_indices)
92 ):
93 raise ValueError("Operations must not have overlapping indices.")
95 return Layer(
96 operations=operations,
97 _unique_indices=unique_indices,
98 _default_simulate_mode=default_simulate_mode,
99 )
101 def add(self, operation: Operation):
102 if self._default_simulate_mode != SimulateMode.HAMILTONIAN and any(
103 ind in self._unique_indices for ind in operation.indices
104 ):
105 raise ValueError("Operations must not have overlapping indices.")
106 self.operations.append(operation)
107 self._unique_indices.extend(operation.indices)
109 def gen_U(self):
110 U = None
112 if len(self.operations) == 0:
113 return None
115 indices_order = []
116 for operation in self.operations:
117 indices_order += operation.indices
119 if U is None:
120 U = operation.gate.U
121 else:
122 U = U ^ operation.gate.U
124 register = self.operations[0].register
125 missing_indices = [
126 i for i in range(len(register.dims)) if i not in indices_order
127 ]
129 for j in missing_indices:
130 U = U ^ identity(register.dims[j])
132 combined_indices = indices_order + missing_indices
134 sorted_ind = list(argsort(combined_indices))
135 U = U.transpose(sorted_ind)
136 return U
138 def gen_Ht(self):
139 Ht = lambda t: 0
141 if len(self.operations) == 0:
142 return Ht
144 for operation in self.operations:
145 def Ht(t, prev_Ht=Ht, prev_operation=operation):
146 return prev_Ht(t) + prev_operation.promote(prev_operation.gate.Ht(t))
148 return Ht
150 def gen_KM(self):
151 KM = Qarray.from_list([])
153 if len(self.operations) == 0:
154 return KM
156 indices_order = []
157 for operation in self.operations:
158 if len(operation.gate.KM) == 0:
159 continue
161 indices_order += operation.indices
163 if len(KM) == 0:
164 KM = deepcopy(operation.gate.KM)
165 else:
166 KM = KM ^ operation.gate.KM
168 if len(KM) == 0:
169 return KM
171 register = self.operations[0].register
172 missing_indices = [
173 i for i in range(len(register.dims)) if i not in indices_order
174 ]
176 for j in missing_indices:
177 KM = KM ^ identity(register.dims[j])
179 combined_indices = indices_order + missing_indices
180 sorted_ind = list(argsort(combined_indices))
182 KM = KM.transpose(sorted_ind)
184 return KM
186 def gen_c_ops(self):
187 c_ops = Qarray.from_list([])
189 if len(self.operations) == 0:
190 return c_ops
192 for operation in self.operations:
193 if len(operation.gate.c_ops) == 0:
194 continue
195 promoted_c_ops = operation.promote(operation.gate.c_ops)
196 c_ops = concatenate([c_ops, promoted_c_ops])
198 return c_ops
200 def gen_ts(self):
201 ts = None
203 for operation in self.operations:
204 if operation.gate.ts is not None and len(operation.gate.ts) > 0:
205 if ts is None:
206 ts = operation.gate.ts
207 else:
208 assert jnp.array_equal(ts, operation.gate.ts), (
209 "All operations in a layer must have the same specified time steps, but not all operations need to have time steps."
210 )
211 return ts
213@struct.dataclass
214class Circuit:
215 register: Register
216 layers: List[Layer] = struct.field(pytree_node=False)
218 @classmethod
219 def create(cls, register: Register, layers: Optional[List[Layer]] = None):
220 if layers is None:
221 layers = []
223 return Circuit(
224 register=register,
225 layers=layers,
226 )
228 def append_layer(self, layer: Layer):
229 self.layers.append(layer)
231 def append_operation(
232 self, operation: Operation, default_simulate_mode: Optional[SimulateMode] = None, new_layer: bool =True
233 ):
234 assert operation.register == self.register, (
235 f"Mismatch in operation register {operation.register} and circuit register {self.register}."
236 )
238 new_layer = new_layer or len(self.layers) == 0
240 if new_layer:
241 default_simulate_mode = default_simulate_mode if default_simulate_mode is not None else SimulateMode.UNITARY
242 self.append_layer(
243 Layer.create([operation], default_simulate_mode=default_simulate_mode)
244 )
245 else:
246 if default_simulate_mode is not None:
247 assert (
248 self.layers[-1]._default_simulate_mode == default_simulate_mode
249 ), "Cannot append operation to last layer with different default simulate mode."
251 self.layers[-1].add(operation)
253 def append(
254 self,
255 gate: Gate,
256 indices: Union[int, List[int]],
257 default_simulate_mode: Optional[SimulateMode] = None,
258 new_layer: bool = True,
259 ):
260 operation = Operation.create(gate, indices, self.register)
261 self.append_operation(operation, default_simulate_mode=default_simulate_mode, new_layer=new_layer)