Coverage for jaxquantum/circuits/circuits.py: 89%
151 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-27 22:28 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-27 22:28 +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 gate_map = operation.gate.KM
159 if len(gate_map) == 0:
160 continue
162 indices_order += operation.indices
164 if len(KM) == 0:
165 KM = deepcopy(gate_map)
166 else:
167 KM = KM ^ gate_map
169 if len(KM) == 0:
170 return KM
172 register = self.operations[0].register
173 missing_indices = [
174 i for i in range(len(register.dims)) if i not in indices_order
175 ]
177 for j in missing_indices:
178 KM = KM ^ identity(register.dims[j])
180 combined_indices = indices_order + missing_indices
181 sorted_ind = list(argsort(combined_indices))
183 KM = KM.transpose(sorted_ind)
185 return KM
187 def gen_c_ops(self):
188 c_ops = Qarray.from_list([])
190 if len(self.operations) == 0:
191 return c_ops
193 for operation in self.operations:
194 if len(operation.gate.c_ops) == 0:
195 continue
196 promoted_c_ops = operation.promote(operation.gate.c_ops)
197 c_ops = concatenate([c_ops, promoted_c_ops])
199 return c_ops
201 def gen_ts(self):
202 ts = None
204 for operation in self.operations:
205 if operation.gate.ts is not None and len(operation.gate.ts) > 0:
206 if ts is None:
207 ts = operation.gate.ts
208 else:
209 assert jnp.array_equal(ts, operation.gate.ts), (
210 "All operations in a layer must have the same specified time steps, but not all operations need to have time steps."
211 )
212 return ts
214@struct.dataclass
215class Circuit:
216 register: Register
217 layers: List[Layer] = struct.field(pytree_node=False)
219 @classmethod
220 def create(cls, register: Register, layers: Optional[List[Layer]] = None):
221 if layers is None:
222 layers = []
224 return Circuit(
225 register=register,
226 layers=layers,
227 )
229 def append_layer(self, layer: Layer):
230 self.layers.append(layer)
232 def append_operation(
233 self, operation: Operation, default_simulate_mode: Optional[SimulateMode] = None, new_layer: bool =True
234 ):
235 assert operation.register == self.register, (
236 f"Mismatch in operation register {operation.register} and circuit register {self.register}."
237 )
239 new_layer = new_layer or len(self.layers) == 0
241 if new_layer:
242 default_simulate_mode = default_simulate_mode if default_simulate_mode is not None else SimulateMode.UNITARY
243 self.append_layer(
244 Layer.create([operation], default_simulate_mode=default_simulate_mode)
245 )
246 else:
247 if default_simulate_mode is not None:
248 assert (
249 self.layers[-1]._default_simulate_mode == default_simulate_mode
250 ), "Cannot append operation to last layer with different default simulate mode."
252 self.layers[-1].add(operation)
254 def append(
255 self,
256 gate: Gate,
257 indices: Union[int, List[int]],
258 default_simulate_mode: Optional[SimulateMode] = None,
259 new_layer: bool = True,
260 ):
261 operation = Operation.create(gate, indices, self.register)
262 self.append_operation(operation, default_simulate_mode=default_simulate_mode, new_layer=new_layer)