0
# Protocols and Extensibility
1
2
The protocols module provides Cirq's extensible protocol system based on structural subtyping. Protocols define interfaces that allow custom classes to integrate seamlessly with Cirq's quantum operations and enable polymorphic behavior without inheritance.
3
4
## Protocol System Overview
5
6
Cirq uses protocols (similar to type classes or traits) to provide extensible behavior for quantum objects. Any class can implement protocol methods to participate in Cirq's ecosystem.
7
8
## Core Protocol Functions
9
10
### Unitary Protocols
11
12
Protocols for working with unitary matrices and operations.
13
14
```python { .api }
15
def has_unitary(val: Any) -> bool:
16
"""Check if object has a unitary matrix representation."""
17
18
def unitary(val: Any) -> np.ndarray:
19
"""Get the unitary matrix of a quantum operation.
20
21
Args:
22
val: Object that may have a unitary representation
23
24
Returns:
25
Unitary matrix as numpy array
26
27
Raises:
28
TypeError: If val doesn't have a unitary representation
29
"""
30
31
def apply_unitary(val: Any, args: 'ApplyUnitaryArgs') -> Union[np.ndarray, None]:
32
"""Apply unitary effect to a state vector or density matrix.
33
34
Args:
35
val: Object with unitary effect
36
args: Arguments specifying target state and workspace
37
38
Returns:
39
Modified state array, or None if not implemented
40
"""
41
42
def apply_unitaries(vals: Iterable[Any], qubits: Sequence['cirq.Qid'],
43
args: 'ApplyUnitaryArgs') -> Union[np.ndarray, None]:
44
"""Apply multiple unitaries in sequence."""
45
```
46
47
### Channel Protocols
48
49
Protocols for quantum channels and noise operations.
50
51
```python { .api }
52
def has_kraus(val: Any) -> bool:
53
"""Check if object has Kraus representation."""
54
55
def kraus(val: Any) -> Tuple[np.ndarray, ...]:
56
"""Get Kraus operators of a quantum channel.
57
58
Returns:
59
Tuple of Kraus operator matrices
60
"""
61
62
def apply_channel(val: Any, args: 'ApplyChannelArgs') -> Union[np.ndarray, None]:
63
"""Apply quantum channel to a state.
64
65
Args:
66
val: Object representing quantum channel
67
args: Arguments specifying target state and workspace
68
69
Returns:
70
Modified state array, or None if not implemented
71
"""
72
73
def has_mixture(val: Any) -> bool:
74
"""Check if object has mixture representation."""
75
76
def mixture(val: Any) -> Tuple[Tuple[float, Any], ...]:
77
"""Get mixture representation as probability-unitary pairs."""
78
79
def apply_mixture(val: Any, args: 'ApplyMixtureArgs') -> Union[np.ndarray, None]:
80
"""Apply mixture representation to a state."""
81
```
82
83
### Decomposition Protocols
84
85
Protocols for decomposing operations into simpler components.
86
87
```python { .api }
88
def decompose(val: Any, *, intercepting_decomposer: Callable = None,
89
fallback_decomposer: Callable = None,
90
keep: Callable[[Any], bool] = None,
91
on_stuck_raise: Union[Exception, Callable[[], Exception]] = None) -> List[Any]:
92
"""Recursively decompose a value into simpler operations.
93
94
Args:
95
val: Object to decompose
96
intercepting_decomposer: Function called before default decomposition
97
fallback_decomposer: Function called if default decomposition fails
98
keep: Predicate for operations that should not be decomposed further
99
on_stuck_raise: Exception to raise if decomposition gets stuck
100
101
Returns:
102
List of decomposed operations
103
"""
104
105
def decompose_once(val: Any, *, default: Any = None) -> Union[List[Any], Any]:
106
"""Decompose value one level.
107
108
Args:
109
val: Object to decompose
110
default: Value to return if decomposition fails
111
112
Returns:
113
List of operations from one level of decomposition, or default
114
"""
115
116
def decompose_once_with_qubits(val: Any, qubits: Sequence['cirq.Qid'],
117
*, default: Any = None) -> Union[List[Any], Any]:
118
"""Decompose with explicit qubit specification."""
119
```
120
121
### Comparison and Equality Protocols
122
123
```python { .api }
124
def approx_eq(val: Any, other: Any, *, atol: Union[int, float] = 1e-8) -> bool:
125
"""Check approximate equality with tolerance.
126
127
Args:
128
val: First value to compare
129
other: Second value to compare
130
atol: Absolute tolerance for comparison
131
132
Returns:
133
True if values are approximately equal
134
"""
135
136
def equal_up_to_global_phase(val: Any, other: Any, *, atol: float = 1e-8) -> bool:
137
"""Check equality up to global phase factor."""
138
139
def commutes(val1: Any, val2: Any, *, atol: float = 1e-8) -> Union[bool, None]:
140
"""Check if two operations commute.
141
142
Returns:
143
True if they commute, False if they don't, None if unknown
144
"""
145
146
def definitely_commutes(val1: Any, val2: Any, *, atol: float = 1e-8) -> bool:
147
"""Check if operations definitely commute (conservative check)."""
148
```
149
150
### Measurement Protocols
151
152
Protocols for measurement operations and keys.
153
154
```python { .api }
155
def is_measurement(val: Any) -> bool:
156
"""Check if object represents a measurement operation."""
157
158
def measurement_key_name(val: Any) -> Optional[str]:
159
"""Get measurement key name, if any."""
160
161
def measurement_key_obj(val: Any) -> Optional['cirq.MeasurementKey']:
162
"""Get measurement key object, if any."""
163
164
def measurement_key_names(val: Any) -> AbstractSet[str]:
165
"""Get all measurement key names used by an object."""
166
167
def measurement_key_objs(val: Any) -> AbstractSet['cirq.MeasurementKey']:
168
"""Get all measurement key objects used by an object."""
169
170
def measurement_keys_touched(val: Any) -> FrozenSet['cirq.MeasurementKey']:
171
"""Get measurement keys read or written by an operation."""
172
```
173
174
### Parameterization Protocols
175
176
Protocols for parameterized operations.
177
178
```python { .api }
179
def is_parameterized(val: Any) -> bool:
180
"""Check if object contains symbolic parameters."""
181
182
def parameter_names(val: Any) -> AbstractSet[str]:
183
"""Get names of symbolic parameters."""
184
185
def parameter_symbols(val: Any) -> AbstractSet[sympy.Symbol]:
186
"""Get symbolic parameter symbols."""
187
188
def resolve_parameters(val: Any, resolver: 'cirq.ParamResolverOrSimilarType',
189
recursive: bool = True) -> Any:
190
"""Resolve symbolic parameters to concrete values.
191
192
Args:
193
val: Object with parameters to resolve
194
resolver: Maps parameter names/symbols to values
195
recursive: Whether to resolve parameters recursively
196
197
Returns:
198
Object with parameters resolved
199
"""
200
201
def resolve_parameters_once(val: Any, resolver: 'cirq.ParamResolverOrSimilarType') -> Any:
202
"""Resolve parameters one level (non-recursive)."""
203
```
204
205
### Shape and Size Protocols
206
207
```python { .api }
208
def num_qubits(val: Any) -> int:
209
"""Get number of qubits operated on by a gate or operation."""
210
211
def qid_shape(val: Any) -> Tuple[int, ...]:
212
"""Get shape tuple describing qid dimensions.
213
214
Returns:
215
Tuple where each element is the dimension of the corresponding qid
216
"""
217
```
218
219
### Inverse and Power Protocols
220
221
```python { .api }
222
def inverse(val: Any, default: Any = None) -> Any:
223
"""Get inverse of an operation.
224
225
Args:
226
val: Operation to invert
227
default: Value to return if inversion not possible
228
229
Returns:
230
Inverse operation, or default if not invertible
231
"""
232
233
def pow(val: Any, exponent: Any, default: Any = None) -> Any:
234
"""Raise operation to a power.
235
236
Args:
237
val: Operation to exponentiate
238
exponent: Power to raise to
239
default: Value to return if exponentiation not supported
240
241
Returns:
242
Operation raised to given power
243
"""
244
```
245
246
### Phase and Multiplication Protocols
247
248
```python { .api }
249
def phase_by(val: Any, phase_turns: float, qubit_index: int) -> Any:
250
"""Phase operation by rotating a qubit's eigenspace.
251
252
Args:
253
val: Operation to phase
254
phase_turns: Phase in units of full turns (2π radians)
255
qubit_index: Index of qubit to phase
256
257
Returns:
258
Phased operation
259
"""
260
261
def mul(lhs: Any, rhs: Any, default: Any = None) -> Any:
262
"""Multiply/compose two operations."""
263
```
264
265
### Circuit Diagram Protocol
266
267
```python { .api }
268
def circuit_diagram_info(val: Any, *, args: 'CircuitDiagramInfoArgs' = None) -> 'CircuitDiagramInfo':
269
"""Get information for drawing operation in circuit diagrams.
270
271
Args:
272
val: Operation to get diagram info for
273
args: Arguments affecting diagram generation
274
275
Returns:
276
Circuit diagram information
277
"""
278
```
279
280
### QASM Generation Protocol
281
282
```python { .api }
283
def qasm(val: Any, *, args: 'QasmArgs' = None, default: Any = None) -> Union[str, Any]:
284
"""Generate QASM code for an operation.
285
286
Args:
287
val: Operation to convert to QASM
288
args: QASM generation arguments
289
default: Value to return if QASM generation fails
290
291
Returns:
292
QASM string representation
293
"""
294
```
295
296
### Other Protocols
297
298
```python { .api }
299
def act_on(val: Any, args: 'cirq.ActOnArgs', allow_decompose: bool = True) -> bool:
300
"""Apply operation effect to a simulation state.
301
302
Returns:
303
True if operation was applied successfully, False otherwise
304
"""
305
306
def pauli_expansion(val: Any) -> Dict['cirq.PauliString', complex]:
307
"""Get Pauli expansion of an operation."""
308
309
def has_stabilizer_effect(val: Any) -> Optional[bool]:
310
"""Check if operation has stabilizer effect (preserves stabilizer states)."""
311
312
def control_keys(val: Any) -> FrozenSet['cirq.MeasurementKey']:
313
"""Get measurement keys that control this operation."""
314
315
def trace_distance_bound(val: Any) -> Optional[float]:
316
"""Upper bound on trace distance from perfect operation."""
317
```
318
319
## Protocol Support Classes
320
321
### Apply Unitary Support
322
323
```python { .api }
324
class ApplyUnitaryArgs:
325
"""Arguments for apply_unitary protocol method."""
326
327
def __init__(self, target_tensor: np.ndarray,
328
available_buffer: np.ndarray,
329
axes: Sequence[int],
330
default: Any = None) -> None:
331
"""Initialize apply unitary arguments.
332
333
Args:
334
target_tensor: State tensor to modify
335
available_buffer: Workspace tensor of same shape
336
axes: Tensor axes corresponding to qubits
337
default: Default value to return if application fails
338
"""
339
340
@property
341
def target_tensor(self) -> np.ndarray:
342
"""Target tensor to apply unitary to."""
343
344
@property
345
def available_buffer(self) -> np.ndarray:
346
"""Workspace buffer tensor."""
347
348
@property
349
def axes(self) -> Tuple[int, ...]:
350
"""Axes in tensor corresponding to qubits."""
351
352
def subspace_view(self, little_endian_bits_int: int = 0) -> np.ndarray:
353
"""Get view of a computational basis subspace."""
354
355
def swap_target_tensor_for(self, new_target_tensor: np.ndarray) -> 'ApplyUnitaryArgs':
356
"""Return args with different target tensor."""
357
358
class SupportsConsistentApplyUnitary:
359
"""Marker interface for consistent unitary application."""
360
361
class SupportsExplicitHasUnitary:
362
"""Marker interface for explicit unitary support."""
363
```
364
365
### Apply Channel Support
366
367
```python { .api }
368
class ApplyChannelArgs:
369
"""Arguments for apply_channel protocol method."""
370
371
def __init__(self, target_tensor: np.ndarray,
372
out_buffer: np.ndarray,
373
auxiliary_buffer0: np.ndarray,
374
auxiliary_buffer1: np.ndarray,
375
left_axes: Sequence[int],
376
right_axes: Sequence[int]) -> None:
377
"""Initialize apply channel arguments."""
378
379
class ApplyMixtureArgs:
380
"""Arguments for apply_mixture protocol method."""
381
382
def __init__(self, target_tensor: np.ndarray,
383
available_buffer: np.ndarray,
384
axes: Sequence[int]) -> None:
385
"""Initialize apply mixture arguments."""
386
```
387
388
### Circuit Diagram Support
389
390
```python { .api }
391
class CircuitDiagramInfo:
392
"""Information for drawing operations in circuit diagrams."""
393
394
def __init__(self, wire_symbols: Tuple[str, ...],
395
exponent: Any = 1,
396
connected: bool = True,
397
exponent_qubit_index: Optional[int] = None,
398
auto_exponent_parens: bool = True) -> None:
399
"""Initialize circuit diagram info.
400
401
Args:
402
wire_symbols: Symbols to show on each wire
403
exponent: Exponent to display (for gates like X^0.5)
404
connected: Whether to connect symbols with lines
405
exponent_qubit_index: Which wire to show exponent on
406
auto_exponent_parens: Whether to add parentheses around exponent
407
"""
408
409
@property
410
def wire_symbols(self) -> Tuple[str, ...]:
411
"""Symbols for each wire."""
412
413
def with_wire_symbols(self, wire_symbols: Sequence[str]) -> 'CircuitDiagramInfo':
414
"""Return info with different wire symbols."""
415
416
class CircuitDiagramInfoArgs:
417
"""Arguments for circuit diagram info generation."""
418
419
def __init__(self, known_qubits: Optional[Iterable['cirq.Qid']] = None,
420
known_qubit_count: Optional[int] = None,
421
use_unicode_characters: bool = True,
422
precision: Optional[int] = None,
423
label_map: Optional[Dict[str, str]] = None) -> None:
424
"""Initialize circuit diagram args."""
425
426
class LabelEntity:
427
"""Entity for labeling in circuit diagrams."""
428
429
def __init__(self, label: str) -> None:
430
"""Initialize label entity."""
431
```
432
433
### Decomposition Context
434
435
```python { .api }
436
class DecompositionContext:
437
"""Context for operation decomposition."""
438
439
def __init__(self, qubit_manager: Optional['cirq.QubitManager'] = None) -> None:
440
"""Initialize decomposition context."""
441
442
@property
443
def qubit_manager(self) -> Optional['cirq.QubitManager']:
444
"""Qubit manager for allocating ancillas during decomposition."""
445
```
446
447
### QASM Support
448
449
```python { .api }
450
class QasmArgs:
451
"""Arguments for QASM generation."""
452
453
def __init__(self, precision: int = 10,
454
version: str = '2.0',
455
qubit_id_map: Optional[Dict['cirq.Qid', str]] = None,
456
meas_key_id_map: Optional[Dict[str, str]] = None) -> None:
457
"""Initialize QASM arguments."""
458
459
def format_field(self, value: Any) -> str:
460
"""Format a field for QASM output."""
461
462
def validate_version(self, supported_versions: Sequence[str]) -> str:
463
"""Validate QASM version."""
464
```
465
466
## JSON Serialization Support
467
468
### JSON Protocol Classes
469
470
```python { .api }
471
class HasJSONNamespace:
472
"""Interface for objects with JSON namespace."""
473
474
@property
475
@abc.abstractmethod
476
def _json_namespace_(self) -> str:
477
"""JSON namespace for serialization."""
478
479
class JsonResolver:
480
"""Resolver for JSON object deserialization."""
481
482
def __init__(self, cirq_type_to_resolver: Dict[str, Callable] = None) -> None:
483
"""Initialize JSON resolver."""
484
485
def resolve(self, cirq_type: str, obj_dict: Dict[str, Any]) -> Any:
486
"""Resolve JSON object to Python object."""
487
488
class SerializableByKey:
489
"""Interface for objects serializable by key."""
490
491
@property
492
@abc.abstractmethod
493
def _serialization_key_(self) -> str:
494
"""Key used for serialization."""
495
496
DEFAULT_RESOLVERS: List[JsonResolver]
497
"""Default JSON resolvers for Cirq objects."""
498
```
499
500
### JSON Utility Functions
501
502
```python { .api }
503
def to_json(obj: Any) -> str:
504
"""Convert object to JSON string."""
505
506
def read_json(file_or_fn: Union[str, pathlib.Path, IO],
507
resolvers: Sequence[JsonResolver] = None) -> Any:
508
"""Read object from JSON file."""
509
510
def to_json_gzip(obj: Any) -> bytes:
511
"""Convert object to gzip-compressed JSON."""
512
513
def read_json_gzip(file_or_fn: Union[str, pathlib.Path, IO],
514
resolvers: Sequence[JsonResolver] = None) -> Any:
515
"""Read object from gzip-compressed JSON file."""
516
517
def cirq_type_from_json(json_dict: Dict[str, Any],
518
resolvers: Sequence[JsonResolver] = None) -> Any:
519
"""Convert JSON dictionary to Cirq object."""
520
521
def obj_to_dict_helper(obj: Any, attribute_names: Iterable[str],
522
namespace: Optional[str] = None) -> Dict[str, Any]:
523
"""Helper for converting objects to dictionaries."""
524
525
def dataclass_json_dict(obj: Any) -> Dict[str, Any]:
526
"""Convert dataclass to JSON dictionary."""
527
528
def json_cirq_type(cls: type) -> type:
529
"""Decorator to register class for JSON serialization."""
530
531
def json_namespace(namespace: str) -> Callable[[type], type]:
532
"""Decorator to set JSON namespace for a class."""
533
```
534
535
## Protocol Support Interfaces
536
537
All protocol support interfaces follow the naming pattern `Supports*`:
538
539
```python { .api }
540
class SupportsActOn:
541
"""Supports acting on simulation states."""
542
543
class SupportsActOnQubits:
544
"""Supports acting on qubits in simulation."""
545
546
class SupportsApplyChannel:
547
"""Supports applying quantum channels."""
548
549
class SupportsApplyMixture:
550
"""Supports applying mixture representations."""
551
552
class SupportsApproximateEquality:
553
"""Supports approximate equality comparison."""
554
555
class SupportsCircuitDiagramInfo:
556
"""Supports circuit diagram generation."""
557
558
class SupportsCommutes:
559
"""Supports commutativity checking."""
560
561
class SupportsControlKey:
562
"""Supports control keys for conditional operations."""
563
564
class SupportsDecompose:
565
"""Supports operation decomposition."""
566
567
class SupportsDecomposeWithQubits:
568
"""Supports decomposition with explicit qubits."""
569
570
class SupportsEqualUpToGlobalPhase:
571
"""Supports equality up to global phase."""
572
573
class SupportsExplicitQidShape:
574
"""Supports explicit qid shape specification."""
575
576
class SupportsExplicitNumQubits:
577
"""Supports explicit qubit count specification."""
578
579
class SupportsJSON:
580
"""Supports JSON serialization."""
581
582
class SupportsKraus:
583
"""Supports Kraus operator representation."""
584
585
class SupportsMeasurementKey:
586
"""Supports measurement keys."""
587
588
class SupportsMixture:
589
"""Supports mixture representation."""
590
591
class SupportsParameterization:
592
"""Supports symbolic parameterization."""
593
594
class SupportsPauliExpansion:
595
"""Supports Pauli operator expansion."""
596
597
class SupportsPhase:
598
"""Supports phase operations."""
599
600
class SupportsQasm:
601
"""Supports QASM generation."""
602
603
class SupportsQasmWithArgs:
604
"""Supports QASM generation with arguments."""
605
606
class SupportsQasmWithArgsAndQubits:
607
"""Supports QASM generation with arguments and qubits."""
608
609
class SupportsTraceDistanceBound:
610
"""Supports trace distance bounds."""
611
612
class SupportsUnitary:
613
"""Supports unitary matrix representation."""
614
```
615
616
## Measurement Key Utilities
617
618
```python { .api }
619
def with_key_path(operation: 'cirq.Operation', path: Tuple[str, ...]) -> 'cirq.Operation':
620
"""Add key path to measurement operation."""
621
622
def with_key_path_prefix(operation: 'cirq.Operation', prefix: Tuple[str, ...]) -> 'cirq.Operation':
623
"""Add key path prefix to measurement operation."""
624
625
def with_measurement_key_mapping(operation: 'cirq.Operation',
626
key_map: Dict[str, str]) -> 'cirq.Operation':
627
"""Apply measurement key mapping to operation."""
628
629
def with_rescoped_keys(operation: 'cirq.Operation',
630
path: Tuple[str, ...]) -> 'cirq.Operation':
631
"""Rescope measurement keys with path."""
632
```
633
634
## Validation Functions
635
636
```python { .api }
637
def validate_mixture(mixture: Sequence[Tuple[float, Any]]) -> None:
638
"""Validate mixture representation (probabilities sum to 1)."""
639
640
def trace_distance_from_angle_list(angles: Sequence[float]) -> float:
641
"""Calculate trace distance bound from rotation angles."""
642
```
643
644
## Usage Examples
645
646
### Implementing Protocol Methods
647
648
```python
649
import cirq
650
import numpy as np
651
652
class MyGate(cirq.Gate):
653
"""Custom gate implementing multiple protocols."""
654
655
def __init__(self, theta: float):
656
self.theta = theta
657
658
def _num_qubits_(self) -> int:
659
return 1
660
661
# Unitary protocol
662
def _unitary_(self) -> np.ndarray:
663
"""Return unitary matrix."""
664
c, s = np.cos(self.theta/2), np.sin(self.theta/2)
665
return np.array([[c, -1j*s], [-1j*s, c]])
666
667
# Decomposition protocol
668
def _decompose_(self, qubits):
669
"""Decompose into basic gates."""
670
return [cirq.rx(self.theta)(qubits[0])]
671
672
# Circuit diagram protocol
673
def _circuit_diagram_info_(self, args):
674
"""Circuit diagram representation."""
675
return cirq.CircuitDiagramInfo(
676
wire_symbols=(f"MyGate({self.theta:.2f})",)
677
)
678
679
# Parameterization protocol
680
def _is_parameterized_(self) -> bool:
681
"""Check if gate has symbolic parameters."""
682
return isinstance(self.theta, sympy.Symbol)
683
684
# Approximate equality protocol
685
def _approx_eq_(self, other, atol):
686
"""Check approximate equality."""
687
return (isinstance(other, MyGate) and
688
abs(self.theta - other.theta) <= atol)
689
690
# Use the custom gate
691
gate = MyGate(np.pi/4)
692
q = cirq.LineQubit(0)
693
694
# Protocol functions work automatically
695
print(f"Has unitary: {cirq.has_unitary(gate)}")
696
print(f"Unitary matrix:\n{cirq.unitary(gate)}")
697
print(f"Decomposition: {cirq.decompose(gate(q))}")
698
```
699
700
### Using Protocol Functions
701
702
```python
703
import cirq
704
import numpy as np
705
706
# Create various operations
707
h_gate = cirq.H
708
x_gate = cirq.X
709
noise = cirq.depolarize(0.1)
710
measurement = cirq.measure(cirq.LineQubit(0), key='result')
711
712
# Check what protocols they support
713
operations = [h_gate, x_gate, noise, measurement]
714
715
for op in operations:
716
print(f"\nOperation: {op}")
717
print(f" Has unitary: {cirq.has_unitary(op)}")
718
print(f" Has Kraus: {cirq.has_kraus(op)}")
719
print(f" Is measurement: {cirq.is_measurement(op)}")
720
print(f" Is parameterized: {cirq.is_parameterized(op)}")
721
722
# Get protocol information when available
723
if cirq.has_unitary(op):
724
print(f" Unitary shape: {cirq.unitary(op).shape}")
725
if cirq.is_measurement(op):
726
print(f" Measurement key: {cirq.measurement_key_name(op)}")
727
```
728
729
### Parameter Resolution
730
731
```python
732
import cirq
733
import sympy
734
735
# Create parameterized circuit
736
theta, phi = sympy.symbols('theta phi')
737
qubits = cirq.LineQubit.range(2)
738
739
circuit = cirq.Circuit([
740
cirq.rx(theta)(qubits[0]),
741
cirq.ry(phi)(qubits[1]),
742
cirq.CNOT(qubits[0], qubits[1])
743
])
744
745
print(f"Is parameterized: {cirq.is_parameterized(circuit)}")
746
print(f"Parameters: {cirq.parameter_names(circuit)}")
747
748
# Resolve parameters
749
resolver = cirq.ParamResolver({'theta': np.pi/4, 'phi': np.pi/6})
750
resolved_circuit = cirq.resolve_parameters(circuit, resolver)
751
752
print(f"After resolution: {cirq.is_parameterized(resolved_circuit)}")
753
```
754
755
This comprehensive protocol system enables Cirq's extensible architecture and allows custom quantum objects to integrate seamlessly with the framework.