-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecorators.py
More file actions
172 lines (138 loc) · 6.07 KB
/
Copy pathdecorators.py
File metadata and controls
172 lines (138 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
from .context import *
from .p_circuit import *
from typing import Type, TypeVar, Dict, Union, Tuple
import numpy as np
T = TypeVar("T")
def module(cls: Type[T]) -> Type[T]:
"""
Decorator that transforms a class into a probabilistic circuit module.
This decorator provides functionality for managing multiple circuit instances
and synthesizing their combined matrices. The synthesize method supports both
sparse and dense matrix formats.
Modules may declare Port attributes at class level to expose an external
interface. These ports can be connected to internal circuit ports and
participate in synthesis via NoCopyConnection (sharing global indices).
Modules may also contain other @module instances as attributes; their
registered instances are flattened into the parent context automatically
(recursive synthesis).
Args:
cls (Type[T]): Class to be decorated
Returns:
Type[T]: Decorated class with module functionality
Example:
>>> @module
>>> class MyCircuit:
>>> out = Port("out")
>>> def __init__(self):
>>> self.gate1 = ANDGate()
>>> self.out.connect(self.gate1.output, NoCopyConnection)
>>>
>>> circuit = MyCircuit()
>>> J_sparse, h_sparse = circuit.synthesize()
>>> J_dense, h_dense = circuit.synthesize(format='dense')
"""
# Detect Port attributes declared on the class (module interface ports)
port_attrs = {
name: attr for name, attr in cls.__dict__.items() if isinstance(attr, Port)
}
original_new = cls.__new__
original_init = cls.__init__
def __new__(cls_ref, *args, **kwargs):
instance = original_new(cls_ref)
instance._context = ModuleContext() # per-instance context
return instance
def __init__(self, *args, **kwargs):
# Initialize module-level interface ports before original __init__ runs
# so that user code in __init__ can connect them to internal gates
if port_attrs:
idx = 0
for name, port in port_attrs.items():
new_port = Port(name=port.name, width=port.width)
new_port.circuit = self
new_port.index = idx
idx += port.width
setattr(self, name, new_port)
# Register the module itself so its interface ports participate
# in global index assignment before internal gate ports
self._context.register_instance(self)
original_init(self, *args, **kwargs)
for attr_name, attr_value in vars(self).items():
if attr_value is self:
continue
if hasattr(attr_value, "n_pbits"):
self._context.register_instance(attr_value)
elif hasattr(attr_value, "_context"):
# Sub-module: flatten its instances into this context
self._context.register_submodule(attr_value)
def synthesize(self, format: str = "sparse") -> Union[
Tuple[Dict[int, Dict[int, float]], Dict[int, float]],
Tuple[np.ndarray, np.ndarray],
]:
"""
Synthesizes the combined circuit matrices in either sparse or dense format.
Args:
format (str, optional): Output format - 'sparse' or 'dense'. Defaults to 'sparse'.
- 'sparse': Returns adjacency list representation as nested dictionaries
- 'dense': Returns numpy arrays
Returns:
If format=='sparse':
Tuple[Dict[int, Dict[int, float]], Dict[int, float]]:
- J: Adjacency list where J[i][j] is the weight of edge i->j
- h: Dictionary where h[i] is the bias of node i
If format=='dense':
Tuple[np.ndarray, np.ndarray]:
- J: Dense coupling matrix
- h: Dense bias vector
Raises:
ValueError: If format is not 'sparse' or 'dense'
"""
if format not in ["sparse", "dense"]:
raise ValueError("Invalid format. Must be either 'sparse' or 'dense'")
return self._context.synthesize(format=format)
cls.__new__ = __new__
cls.__init__ = __init__
cls.synthesize = synthesize
return cls
def pcircuit(n_pbits: int) -> Callable[[Type[T]], Type[T]]:
"""
Decorator that transforms a class into a probabilistic circuit `PCircuit`.
This decorator adds circuit parameters (h and J matrices) and port management
to the decorated class.
Args:
n_pbits (int): Number of ports/qubits in the circuit
Returns:
Callable[[Type[T]], Type[T]]: Decorator function
Example:
>>> @pcircuit(n_pbits=3)
>>> class ANDGate:
>>> input1 = Port("input1")
>>> input2 = Port("input2")
>>> output = Port("output")
"""
def decorator(cls: Type[T]) -> Type[T]:
# Get port attributes from the class
port_attrs = {
name: attr for name, attr in cls.__dict__.items() if isinstance(attr, Port)
}
class WrappedCircuit(PCircuit, cls):
def __init__(self, *args, **kwargs):
# Initialize PCircuit first
PCircuit.__init__(self, n_pbits, port_attrs)
# Initialize the decorated class
if hasattr(cls, "__init__"):
cls.__init__(self, *args, **kwargs)
# Handle any custom h/J matrices defined in the decorated class
if hasattr(cls, "h"):
if cls.h.shape != (n_pbits, 1):
raise ValueError(
f"h matrix must have shape ({n_pbits}, 1), got {cls.h.shape}"
)
self.h = cls.h
if hasattr(cls, "J"):
if cls.J.shape != (n_pbits, n_pbits):
raise ValueError(
f"J matrix must have shape ({n_pbits}, {n_pbits}), got {cls.J.shape}"
)
self.J = cls.J
return WrappedCircuit
return decorator