Skip to content

circuit/index.py

class Mode

Execution mode for the circuit: statevector (VECTOR), density-matrix (MATRIX), noisy density-matrix (NOISY), or statevector quantum trajectories (TRAJECTORY).

nametypedefault
MATRIXNone'matrix'
NOISYNone'noisy'
TRAJECTORYNone'trajectory'
VECTORNone'vector'

class Frame

Lightweight record of a circuit operation (gate name, target indices, local dims, and parameters).

nametypedefault
dimU[int, Array]None
indexList[int]None
namestrNone
paramsDict[str, Any]None

init

Create an operation frame describing a gate acting on index with given local dim and params.

py
def __init__(self: Any, dim: U[int, Array], index: List[int], name: str, params: Optional[Dict[str, Any]]) -> Any
Implementation
python
def __init__(self, dim: U[int, Array], index: List[int], name: str, params: Optional[Dict[str, Any]]=None):
    self.index = index
    self.dim = dim
    self.name = name
    self.params = params or {}

parse

Extract a small, human-readable parameter subset (e.g. angles/indices) for display/logging.

py
def parse(kwargs: Dict[str, Any]) -> Dict[str, Any] [static]
Implementation
python
def parse(kwargs: Dict[str, Any]) -> Dict[str, Any]:
    valid = ['i', 'j', 'k', 'angle', 'type']
    params: Dict[str, Any] = {}
    for key in valid:
        if key in kwargs:
            val = kwargs[key]
            if isinstance(val, torch.Tensor):
                val = val.item()
            if isinstance(val, float):
                val = round(val, 4)
            params[key] = val
    return params

create

Create a Frame from a gate-like input, inferring a display name and capturing key params.

py
def create(gate_in: Any, dims: List[int], index: List[int]) -> 'Frame' [static]
Implementation
python
def create(gate_in: Any, dims: List[int], index: List[int], **kwargs: Any) -> 'Frame':
    params = Frame.parse(kwargs)
    name = ''
    if hasattr(gate_in, 'name'):
        name = gate_in.name
    elif hasattr(gate_in, '__name__'):
        name = gate_in.__name__
    else:
        name = 'U'
    return Frame(dim=dims, index=index, name=name, params=params)

class Circuit

A qudit circuit as an nn.Module applying a sequence of embedded unitaries. In VECTOR (default) mode it applies

|ψUmU2U1|ψ,

whereas in MATRIX mode it applies the unitary channel,

ρUmU2U1ρU1U2Um.

nametypedefault
circuitnn.SequentialNone
devicestrNone
dimU[int, Array]None
dims_List[int]None
flipboolNone
gate_genAnyNone
gatesDict[int, Gategen]None
modeModeNone
noise_modelOptional[Any]None
operationsList[Frame]None
widthintNone
wiresintNone

init

Initialize a circuit with wires subsystems of local dimension(s) dim on device.

py
def __init__(self: Any, wires: int, dim: U[int, Array], device: str, mode: U[Mode, str], flip: bool, noise: Optional[Any]) -> Any
Implementation
python
def __init__(self, wires: int=2, dim: U[int, Array]=2, device: str='cpu', mode: U[Mode, str]=Mode.VECTOR, flip: bool=False, noise: Optional[Any]=None):
    super(Circuit, self).__init__()
    if isinstance(mode, str):
        mode = mode.lower()
    self.mode = Mode(mode)
    if isinstance(dim, int):
        self.dims_ = [dim] * wires
    elif isinstance(dim, list):
        if len(dim) != wires:
            raise ValueError(f'Dim list {len(dim)} != wires {wires}.')
        self.dims_ = dim
    self.dim = dim
    self.width = int(torch.prod(torch.tensor(self.dims_)).item())
    self.wires = wires
    self.device = device
    self.flip = flip
    self.circuit = nn.Sequential()
    self.operations = []
    udits = sorted(list(set(self.dims_)))
    self.gates = {}
    for d in udits:
        self.gates[d] = Gategen(dim=d, device=device)
    if isinstance(self.dim, int):
        self.gate_gen = self.gates[self.dim]
    if self.mode in (Mode.NOISY, Mode.TRAJECTORY):
        if noise is None:
            raise ValueError(f'Circuit(mode={mode!r}) requires a noise model.')
        self.noise_model = noise
        self._noise_pairs: List[List] = []
        self._noise_module_list = nn.ModuleList()
    else:
        self.noise_model = None

gate

Append a gate acting on index to the circuit with optionally params

Operator may be of type Gate, Operator, torch.Tensor, or a callable factory for an embedded Gate.

The gate is added to the circuit as an nn.Module and a Frame is recorded for display/logging.

py
def gate(self: Any, gate_in: Any, index: Any) -> None
Implementation
python
def gate(self, gate_in: Any, index: Any, **kwargs: Any) -> None:
    if 'device' not in kwargs:
        kwargs['device'] = self.device
    idx_list = index if isinstance(index, list) else [index]
    dims = [self.dims_[i] for i in idx_list]
    self.operations.append(Frame.create(gate_in, dims, idx_list, **kwargs))
    Instance: Any = None
    if isinstance(gate_in, (torch.Tensor, Operator)):
        Instance = Gate(matrix=gate_in, index=idx_list, wires=self.wires, dim=self.dims_, device=self.device)
    elif callable(gate_in):
        Instance = gate_in(dim=self.dims_, wires=self.wires, index=idx_list, **kwargs)
    else:
        raise TypeError(f'Unsupported gate input: {type(gate_in)}')
    pos = str(len(self.circuit))
    self.circuit.add_module(pos, Instance)
    if self.mode in (Mode.NOISY, Mode.TRAJECTORY):
        noise = self.noise_model
        gate_name = self.operations[-1].name
        generator = getattr(noise, 'generator', None)
        if isinstance(noise, CoherentNoise):
            d_local = Instance.target_size
            Instance.U = noise.perturb(Instance.U, d_local).to(dtype=C64)
            self._noise_pairs.append([])
        else:
            pair = []
            for wire in idx_list:
                d = self.dims_[wire]
                K = noise.kraus_for(gate_name, wire, d).to(dtype=C64, device=self.device)
                if self.mode == Mode.NOISY:
                    ng = NoisyGate(K=K, index=[wire], wires=self.wires, dims=self.dims_, generator=generator)
                else:
                    ng = TrajectoryGate(K=K, index=[wire], wires=self.wires, dims=self.dims_, generator=generator)
                pair.append(ng)
                self._noise_module_list.append(ng)
            self._noise_pairs.append(pair)

forward

Apply the circuit to a statevector or density matrix depending on self.mode.

py
def forward(self: Any, x: Any) -> torch.Tensor
Implementation
python
def forward(self, x: Any) -> torch.Tensor:
    if isinstance(x, np.ndarray):
        x = torch.from_numpy(x).to(dtype=C64, device=self.device)
    elif not isinstance(x, torch.Tensor):
        x = torch.tensor(x, dtype=C64, device=self.device)
    else:
        x = x.to(dtype=C64, device=self.device)
    if self.mode == Mode.VECTOR:
        return self.circuit(x)
    W = self.width
    if self.mode == Mode.TRAJECTORY:
        psi = x.reshape(W)
        for gate, noise_gates in zip(self.circuit, self._noise_pairs):
            psi = gate(psi)
            for ng in noise_gates:
                psi = ng(psi.reshape(W))
        return psi.reshape(W)
    if x.dim() == 1 or (x.dim() == 2 and min(x.shape) == 1):
        psi = x.reshape(W, 1)
        rho = psi @ psi.conj().T
    else:
        rho = x
    if self.mode == Mode.NOISY:
        for gate, noise_gates in zip(self.circuit, self._noise_pairs):
            rho = gate.forwardd(rho)
            for ng in noise_gates:
                rho = ng.forwardd(rho)
        return rho
    for module in self.circuit:
        rho = module.forwardd(rho)
    return rho

matrix

Materialize the full unitary matrix by acting on the computational basis vectors.

py
def matrix(self: Any, littleEndian: Any) -> torch.Tensor
Implementation
python
def matrix(self, littleEndian=False) -> torch.Tensor:
    W = self.width
    I = torch.eye(W, dtype=C64, device=self.device)
    cols = []
    for i in range(W):
        res = self.circuit(I[i])
        if littleEndian:
            res = LittleEndian(res, self.dims_)
        cols.append(res.reshape(-1, 1))
    return torch.cat(cols, dim=1)

expectation

Compute ψ|O|ψ (VECTOR mode) or Tr(Oρ) (MATRIX/NOISY mode) where |ψ / ρ is the output of forward(state).

py
def expectation(self: Any, operator: Any, state: torch.Tensor) -> torch.Tensor
Implementation
python
def expectation(self, operator: Any, state: torch.Tensor) -> torch.Tensor:
    out = self.forward(state)
    if not isinstance(operator, torch.Tensor):
        op = torch.tensor(operator, dtype=C64, device=self.device)
    else:
        op = operator.to(dtype=C64, device=self.device)
    if self.mode == Mode.VECTOR:
        psi = out.reshape(-1)
        return torch.real(psi.conj() @ (op @ psi))
    else:
        return torch.real(torch.trace(op @ out))

sample

Sample shots bitstring outcomes from p(x)=|x|ψ|2 where |ψ=forward(state). Returns dict[bitstring, count].

py
def sample(self: Any, state: torch.Tensor, shots: int) -> dict
Implementation
python
def sample(self, state: torch.Tensor, shots: int=1024) -> dict:
    out = self.forward(state)
    if self.mode == Mode.VECTOR:
        probs = out.reshape(-1).abs().pow(2).real.float()
    else:
        probs = torch.diag(out).real.float()
    indices = torch.multinomial(probs, num_samples=shots, replacement=True)
    result: dict = {}
    for idx in indices.tolist():
        bits = []
        n = idx
        for d in reversed(self.dims_):
            bits.append(str(n % d))
            n //= d
        key = ''.join(reversed(bits))
        result[key] = result.get(key, 0) + 1
    return result

cut

Mark a gate position as a circuit cut using the given Coupler.

index = [c_A, c_B]: the two wires the cut gate acts on. split: wire index where partition A ends and B begins (wires 0..split-1 → A, wires split..n-1 → B). For adjacent cuts (c_B == c_A + 1) split defaults to c_B. For non-adjacent gates split must be given explicitly. The gate is NOT added to the circuit — it is replaced by the Coupler decomposition when run_cut() is called.

py
def cut(self: Any, coupler: Any, index: Any, split: Optional[int]) -> None
Implementation
python
def cut(self, coupler: Any, index: Any, split: Optional[int]=None) -> None:
    if not hasattr(self, '_cuts'):
        self._cuts: list = []
    idx = index if isinstance(index, list) else list(index)
    c_A, c_B = idx
    if split is None:
        if c_B != c_A + 1:
            raise ValueError(f'Cut wires {c_A} and {c_B} are not adjacent — provide split= to specify where partition A ends and B begins.')
        split = c_B
    if not c_A < split <= c_B:
        raise ValueError(f'split={split} must satisfy c_A < split <= c_B ({c_A} < split <= {c_B}).')
    self._cuts.append((coupler, idx, split, len(self.circuit)))

run_cut

Execute the circuit using circuit cutting at the marked cut position.

Partitions wires at the cut: partition A = wires 0..split-1, partition B = wires split..end. Gates on each partition are split into pre- and post-cut segments; sub-circuits run as pre → cut_op → post. The joint probability distribution is reconstructed via amplitude superposition over the Coupler decomposition.

py
def run_cut(self: Any) -> dict
Implementation
python
def run_cut(self) -> dict:
    from ..cut.utils import matrix_of
    if self.mode == Mode.TRAJECTORY:
        raise NotImplementedError('Circuit cutting is not supported for TRAJECTORY mode — stochastic collapse per term makes amplitude reconstruction invalid.')
    cuts = getattr(self, '_cuts', [])
    if not cuts:
        raise ValueError('No cuts defined — call cut() before run_cut().')
    if len(cuts) > 1:
        from ..cut.multi import run_multi
        return run_multi(cuts, self.wires, self.dims_, self.circuit, self.device)
    coupler, [c_A, c_B], split, cut_pos = cuts[0]
    partA = set(range(0, split))
    partB = set(range(split, self.wires))
    dims_A = [self.dims_[w] for w in sorted(partA)]
    dims_B = [self.dims_[w] for w in sorted(partB)]
    preA, postA = ([], [])
    preB, postB = ([], [])
    for i, gate in enumerate(self.circuit.children()):
        hasA = any((w in partA for w in gate.index))
        hasB = any((w in partB for w in gate.index))
        if hasA and hasB:
            raise ValueError(f'Gate on wires {gate.index} spans both partitions. Only the marked cut gate may cross the partition boundary.')
        if hasA:
            (preA if i < cut_pos else postA).append((matrix_of(gate), gate.index))
        elif hasB:
            (preB if i < cut_pos else postB).append((matrix_of(gate), [w - split for w in gate.index]))
    device = self.device

    def subA(op):
        qc = Circuit(len(partA), dim=dims_A, device=device)
        for U, idx in preA:
            qc.gate(U, idx)
        qc.gate(op, [c_A])
        for U, idx in postA:
            qc.gate(U, idx)
        return qc

    def subB(op):
        qc = Circuit(len(partB), dim=dims_B, device=device)
        for U, idx in preB:
            qc.gate(U, idx)
        qc.gate(op, [c_B - split])
        for U, idx in postB:
            qc.gate(U, idx)
        return qc
    return coupler.run(subA, subB)

draw

Render a simple textual diagram of the circuit; currently supports mode='ascii'.

py
def draw(self: Any, mode: str) -> Any
Implementation
python
def draw(self, mode: str='ascii') -> Any:
    from .transform import Table
    table = Table(self)
    if mode == 'ascii':
        return table.draw(self)
    return table.draw(self)