l0-python 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PolicyEngine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: l0-python
3
+ Version: 0.1.0
4
+ Summary: L0 regularization for sparse neural networks and intelligent sampling
5
+ Author-email: PolicyEngine <hello@policyengine.org>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 PolicyEngine
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/PolicyEngine/L0
29
+ Project-URL: Repository, https://github.com/PolicyEngine/L0
30
+ Project-URL: Documentation, https://github.com/PolicyEngine/L0#readme
31
+ Project-URL: Issues, https://github.com/PolicyEngine/L0/issues
32
+ Keywords: l0,regularization,sparsity,neural networks,calibration
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
39
+ Requires-Python: >=3.13
40
+ Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Requires-Dist: torch>=2.0.0
43
+ Requires-Dist: numpy>=1.20.0
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest>=7.0; extra == "dev"
46
+ Requires-Dist: pytest-cov>=3.0; extra == "dev"
47
+ Requires-Dist: black>=22.0; extra == "dev"
48
+ Requires-Dist: mypy>=0.900; extra == "dev"
49
+ Requires-Dist: ruff>=0.0.280; extra == "dev"
50
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
51
+ Requires-Dist: build>=1.3; extra == "dev"
52
+ Provides-Extra: docs
53
+ Requires-Dist: mystmd>=1.3; extra == "docs"
54
+ Requires-Dist: jupyter>=1.0; extra == "docs"
55
+ Requires-Dist: notebook>=7.0; extra == "docs"
56
+ Requires-Dist: ipykernel>=6.0; extra == "docs"
57
+ Provides-Extra: examples
58
+ Requires-Dist: matplotlib>=3.3; extra == "examples"
59
+ Requires-Dist: tqdm>=4.60; extra == "examples"
60
+ Requires-Dist: pandas>=1.3; extra == "examples"
61
+ Requires-Dist: scikit-learn>=1.0; extra == "examples"
62
+ Requires-Dist: torchvision>=0.15; extra == "examples"
63
+ Dynamic: license-file
64
+
65
+ # L0 Regularization
66
+
67
+ A PyTorch implementation of L0 regularization for neural network sparsification and intelligent sampling, based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312).
68
+
69
+ ## Features
70
+
71
+ - **Hard Concrete Distribution**: Differentiable approximation of L0 norm
72
+ - **Sparse Neural Network Layers**: L0Linear, L0Conv2d with automatic pruning
73
+ - **Intelligent Sampling**: Sample/feature selection gates for calibration
74
+ - **L0L2 Combined Penalty**: Recommended approach to prevent overfitting
75
+ - **Temperature Scheduling**: Annealing for improved convergence
76
+ - **TDD Development**: Comprehensive test coverage
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ pip install l0
82
+ ```
83
+
84
+ For development:
85
+ ```bash
86
+ git clone https://github.com/PolicyEngine/L0.git
87
+ cd L0
88
+ pip install -e .[dev]
89
+ ```
90
+
91
+ ## Quick Start
92
+
93
+ ### Neural Network Sparsification
94
+
95
+ ```python
96
+ import torch
97
+ from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
98
+
99
+ # Create a sparse model
100
+ class SparseModel(torch.nn.Module):
101
+ def __init__(self):
102
+ super().__init__()
103
+ self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
104
+ self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
105
+
106
+ def forward(self, x):
107
+ x = torch.relu(self.fc1(x))
108
+ return self.fc2(x)
109
+
110
+ model = SparseModel()
111
+ optimizer = torch.optim.Adam(model.parameters())
112
+ scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
113
+
114
+ # Training loop
115
+ for epoch in range(100):
116
+ # Update temperature
117
+ temp = scheduler.get_temperature(epoch)
118
+ update_temperatures(model, temp)
119
+
120
+ # Forward pass
121
+ output = model(input_data)
122
+ ce_loss = criterion(output, target)
123
+
124
+ # Add L0L2 penalty
125
+ penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
126
+ loss = ce_loss + penalty
127
+
128
+ # Backward pass
129
+ optimizer.zero_grad()
130
+ loss.backward()
131
+ optimizer.step()
132
+ ```
133
+
134
+ ### Intelligent Sample Selection
135
+
136
+ ```python
137
+ from l0 import SampleGate, HybridGate
138
+
139
+ # Pure L0 selection
140
+ gate = SampleGate(n_samples=10000, target_samples=1000)
141
+ selected_data, indices = gate.select_samples(data)
142
+
143
+ # Hybrid selection (25% L0, 75% random)
144
+ hybrid = HybridGate(
145
+ n_items=10000,
146
+ l0_fraction=0.25,
147
+ random_fraction=0.75,
148
+ target_items=1000
149
+ )
150
+ selected, indices, types = hybrid.select(data)
151
+ ```
152
+
153
+ ### Feature Selection
154
+
155
+ ```python
156
+ from l0 import FeatureGate
157
+
158
+ # Select top features
159
+ gate = FeatureGate(n_features=1000, max_features=50)
160
+ selected_data, feature_indices = gate.select_features(data)
161
+
162
+ # Get feature importance
163
+ importance = gate.get_feature_importance()
164
+ ```
165
+
166
+ ## Integration with PolicyEngine
167
+
168
+ This package is designed to work with PolicyEngine's calibration system:
169
+
170
+ ```python
171
+ # In policyengine-us-data or similar
172
+ from l0 import HardConcrete
173
+
174
+ # Use for household selection in CPS calibration
175
+ gates = HardConcrete(
176
+ len(household_weights),
177
+ temperature=0.25,
178
+ init_mean=0.999 # Start with most households
179
+ )
180
+
181
+ # Apply gates during reweighting
182
+ masked_weights = weights * gates()
183
+ ```
184
+
185
+ ## Documentation
186
+
187
+ Full documentation available at: https://policyengine.github.io/L0/
188
+
189
+ ## Testing
190
+
191
+ Run tests with:
192
+ ```bash
193
+ pytest tests/ -v --cov=l0
194
+ ```
195
+
196
+ ## Acknowledgments
197
+
198
+ This implementation is inspired by and builds upon the [original L0 regularization code](https://github.com/AMLab-Amsterdam/L0_regularization) by AMLab Amsterdam, which accompanied the paper by Louizos et al. (2018).
199
+
200
+ ## Citation
201
+
202
+ If you use this package, please cite:
203
+
204
+ ```bibtex
205
+ @article{louizos2017learning,
206
+ title={Learning Sparse Neural Networks through L0 Regularization},
207
+ author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
208
+ journal={arXiv preprint arXiv:1712.01312},
209
+ year={2017}
210
+ }
211
+ ```
212
+
213
+ ## License
214
+
215
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,151 @@
1
+ # L0 Regularization
2
+
3
+ A PyTorch implementation of L0 regularization for neural network sparsification and intelligent sampling, based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312).
4
+
5
+ ## Features
6
+
7
+ - **Hard Concrete Distribution**: Differentiable approximation of L0 norm
8
+ - **Sparse Neural Network Layers**: L0Linear, L0Conv2d with automatic pruning
9
+ - **Intelligent Sampling**: Sample/feature selection gates for calibration
10
+ - **L0L2 Combined Penalty**: Recommended approach to prevent overfitting
11
+ - **Temperature Scheduling**: Annealing for improved convergence
12
+ - **TDD Development**: Comprehensive test coverage
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install l0
18
+ ```
19
+
20
+ For development:
21
+ ```bash
22
+ git clone https://github.com/PolicyEngine/L0.git
23
+ cd L0
24
+ pip install -e .[dev]
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### Neural Network Sparsification
30
+
31
+ ```python
32
+ import torch
33
+ from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
34
+
35
+ # Create a sparse model
36
+ class SparseModel(torch.nn.Module):
37
+ def __init__(self):
38
+ super().__init__()
39
+ self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
40
+ self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
41
+
42
+ def forward(self, x):
43
+ x = torch.relu(self.fc1(x))
44
+ return self.fc2(x)
45
+
46
+ model = SparseModel()
47
+ optimizer = torch.optim.Adam(model.parameters())
48
+ scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
49
+
50
+ # Training loop
51
+ for epoch in range(100):
52
+ # Update temperature
53
+ temp = scheduler.get_temperature(epoch)
54
+ update_temperatures(model, temp)
55
+
56
+ # Forward pass
57
+ output = model(input_data)
58
+ ce_loss = criterion(output, target)
59
+
60
+ # Add L0L2 penalty
61
+ penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
62
+ loss = ce_loss + penalty
63
+
64
+ # Backward pass
65
+ optimizer.zero_grad()
66
+ loss.backward()
67
+ optimizer.step()
68
+ ```
69
+
70
+ ### Intelligent Sample Selection
71
+
72
+ ```python
73
+ from l0 import SampleGate, HybridGate
74
+
75
+ # Pure L0 selection
76
+ gate = SampleGate(n_samples=10000, target_samples=1000)
77
+ selected_data, indices = gate.select_samples(data)
78
+
79
+ # Hybrid selection (25% L0, 75% random)
80
+ hybrid = HybridGate(
81
+ n_items=10000,
82
+ l0_fraction=0.25,
83
+ random_fraction=0.75,
84
+ target_items=1000
85
+ )
86
+ selected, indices, types = hybrid.select(data)
87
+ ```
88
+
89
+ ### Feature Selection
90
+
91
+ ```python
92
+ from l0 import FeatureGate
93
+
94
+ # Select top features
95
+ gate = FeatureGate(n_features=1000, max_features=50)
96
+ selected_data, feature_indices = gate.select_features(data)
97
+
98
+ # Get feature importance
99
+ importance = gate.get_feature_importance()
100
+ ```
101
+
102
+ ## Integration with PolicyEngine
103
+
104
+ This package is designed to work with PolicyEngine's calibration system:
105
+
106
+ ```python
107
+ # In policyengine-us-data or similar
108
+ from l0 import HardConcrete
109
+
110
+ # Use for household selection in CPS calibration
111
+ gates = HardConcrete(
112
+ len(household_weights),
113
+ temperature=0.25,
114
+ init_mean=0.999 # Start with most households
115
+ )
116
+
117
+ # Apply gates during reweighting
118
+ masked_weights = weights * gates()
119
+ ```
120
+
121
+ ## Documentation
122
+
123
+ Full documentation available at: https://policyengine.github.io/L0/
124
+
125
+ ## Testing
126
+
127
+ Run tests with:
128
+ ```bash
129
+ pytest tests/ -v --cov=l0
130
+ ```
131
+
132
+ ## Acknowledgments
133
+
134
+ This implementation is inspired by and builds upon the [original L0 regularization code](https://github.com/AMLab-Amsterdam/L0_regularization) by AMLab Amsterdam, which accompanied the paper by Louizos et al. (2018).
135
+
136
+ ## Citation
137
+
138
+ If you use this package, please cite:
139
+
140
+ ```bibtex
141
+ @article{louizos2017learning,
142
+ title={Learning Sparse Neural Networks through L0 Regularization},
143
+ author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
144
+ journal={arXiv preprint arXiv:1712.01312},
145
+ year={2017}
146
+ }
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,53 @@
1
+ """
2
+ L0 Regularization Package
3
+
4
+ A PyTorch implementation of L0 regularization for neural network sparsification
5
+ and intelligent sampling, based on Louizos, Welling, & Kingma (2017).
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ from .distributions import HardConcrete
11
+ from .gates import FeatureGate, HybridGate, L0Gate, SampleGate
12
+ from .layers import (
13
+ L0Conv2d,
14
+ L0DepthwiseConv2d,
15
+ L0Linear,
16
+ SparseMLP,
17
+ prune_model,
18
+ )
19
+ from .penalties import (
20
+ PenaltyTracker,
21
+ TemperatureScheduler,
22
+ compute_l0_penalty,
23
+ compute_l0l2_penalty,
24
+ compute_l2_penalty,
25
+ get_active_parameter_count,
26
+ get_sparsity_stats,
27
+ update_temperatures,
28
+ )
29
+
30
+ __all__ = [
31
+ # Distributions
32
+ "HardConcrete",
33
+ # Layers
34
+ "L0Linear",
35
+ "L0Conv2d",
36
+ "L0DepthwiseConv2d",
37
+ "SparseMLP",
38
+ "prune_model",
39
+ # Gates
40
+ "L0Gate",
41
+ "SampleGate",
42
+ "FeatureGate",
43
+ "HybridGate",
44
+ # Penalties
45
+ "compute_l0_penalty",
46
+ "compute_l2_penalty",
47
+ "compute_l0l2_penalty",
48
+ "get_sparsity_stats",
49
+ "get_active_parameter_count",
50
+ "TemperatureScheduler",
51
+ "update_temperatures",
52
+ "PenaltyTracker",
53
+ ]
@@ -0,0 +1,205 @@
1
+ """
2
+ Hard Concrete distribution for L0 regularization.
3
+
4
+ Based on Louizos, Welling, & Kingma (2017): "Learning Sparse Neural Networks
5
+ through L0 Regularization" (https://arxiv.org/abs/1712.01312)
6
+ """
7
+
8
+ import math
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+
14
+ class HardConcrete(nn.Module):
15
+ """
16
+ Hard Concrete distribution for L0 regularization.
17
+
18
+ This distribution enables differentiable approximation of the L0 norm
19
+ by using stochastic gates that can be optimized via gradient descent.
20
+
21
+ Parameters
22
+ ----------
23
+ input_dim : int
24
+ Size of the first dimension (e.g., number of samples or features)
25
+ output_dim : Optional[int]
26
+ Size of the second dimension for 2D gates (e.g., for weight matrices)
27
+ temperature : float
28
+ Temperature parameter controlling the hardness of the distribution.
29
+ Lower values make the distribution more discrete.
30
+ stretch : float
31
+ Stretch parameter for the hard concrete transformation
32
+ init_mean : float
33
+ Initial mean activation probability for the gates (0 to 1)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ input_dim: int,
39
+ output_dim: int | None = None,
40
+ temperature: float = 0.5,
41
+ stretch: float = 0.1,
42
+ init_mean: float = 0.5,
43
+ ):
44
+ super().__init__()
45
+
46
+ # Set gate dimensions
47
+ if output_dim is None:
48
+ self.gate_size: tuple[int, ...] = (input_dim,)
49
+ else:
50
+ self.gate_size = (input_dim, output_dim)
51
+
52
+ # Learnable parameters - logits for each gate
53
+ self.qz_logits = nn.Parameter(torch.zeros(self.gate_size))
54
+
55
+ # Distribution parameters
56
+ self.temperature = temperature
57
+ self.stretch = stretch
58
+ self.gamma = -stretch # Lower bound after stretching
59
+ self.zeta = 1.0 + stretch # Upper bound after stretching
60
+ self.init_mean = init_mean
61
+
62
+ # Initialize parameters
63
+ self.reset_parameters()
64
+
65
+ def reset_parameters(self) -> None:
66
+ """Initialize gate logits based on desired initial mean."""
67
+ if self.init_mean is not None:
68
+ # Convert mean probability to logit space
69
+ init_val = math.log(self.init_mean / (1 - self.init_mean))
70
+ self.qz_logits.data.fill_(init_val)
71
+
72
+ def forward(
73
+ self, input_shape: tuple[int, ...] | None = None
74
+ ) -> torch.Tensor:
75
+ """
76
+ Sample or compute gates.
77
+
78
+ Parameters
79
+ ----------
80
+ input_shape : Optional[Tuple[int, ...]]
81
+ Shape of the input tensor for broadcasting gates if needed
82
+
83
+ Returns
84
+ -------
85
+ torch.Tensor
86
+ Gate values in [0, 1], either sampled (training) or deterministic (eval)
87
+ """
88
+ if self.training:
89
+ gates = self._sample_gates()
90
+ else:
91
+ gates = self._deterministic_gates()
92
+
93
+ # Broadcast gates if needed for convolution layers
94
+ if input_shape is not None and len(input_shape) > len(gates.shape):
95
+ # Add dimensions for spatial dims (e.g., height, width in Conv2d)
96
+ for _ in range(len(input_shape) - len(gates.shape)):
97
+ gates = gates.unsqueeze(-1)
98
+
99
+ return gates
100
+
101
+ def _sample_gates(self) -> torch.Tensor:
102
+ """
103
+ Sample gates using the reparameterization trick.
104
+
105
+ Returns
106
+ -------
107
+ torch.Tensor
108
+ Sampled gate values in [0, 1]
109
+ """
110
+ # Sample uniform noise (avoiding exact 0 and 1 for numerical stability)
111
+ u = torch.zeros_like(self.qz_logits).uniform_(1e-8, 1.0 - 1e-8)
112
+
113
+ # Apply the concrete distribution transformation
114
+ # s = sigmoid((log(u) - log(1-u) + logits) / temperature)
115
+ s = torch.log(u) - torch.log(1 - u) + self.qz_logits
116
+ s = torch.sigmoid(s / self.temperature)
117
+
118
+ # Stretch and clamp to create hard concrete
119
+ s = s * (self.zeta - self.gamma) + self.gamma
120
+ gates = torch.clamp(s, 0, 1)
121
+
122
+ return gates
123
+
124
+ def _deterministic_gates(self) -> torch.Tensor:
125
+ """
126
+ Compute deterministic gates for evaluation.
127
+
128
+ Returns
129
+ -------
130
+ torch.Tensor
131
+ Deterministic gate values in [0, 1]
132
+ """
133
+ # Use mean of the distribution
134
+ probs = torch.sigmoid(self.qz_logits)
135
+
136
+ # Apply stretching transformation
137
+ gates = probs * (self.zeta - self.gamma) + self.gamma
138
+
139
+ return torch.clamp(gates, 0, 1)
140
+
141
+ def get_penalty(self) -> torch.Tensor:
142
+ """
143
+ Compute the expected L0 norm (number of active gates).
144
+
145
+ Returns
146
+ -------
147
+ torch.Tensor
148
+ Expected number of non-zero gates
149
+ """
150
+ # Shift logits to account for hard concrete bounds
151
+ logits_shifted = self.qz_logits - self.temperature * math.log(
152
+ -self.gamma / self.zeta
153
+ )
154
+
155
+ # Probability that gate is active (non-zero)
156
+ prob_active = torch.sigmoid(logits_shifted)
157
+
158
+ return prob_active.sum()
159
+
160
+ def get_active_prob(self) -> torch.Tensor:
161
+ """
162
+ Get the probability that each gate is active.
163
+
164
+ Returns
165
+ -------
166
+ torch.Tensor
167
+ Probability of each gate being non-zero
168
+ """
169
+ logits_shifted = self.qz_logits - self.temperature * math.log(
170
+ -self.gamma / self.zeta
171
+ )
172
+ return torch.sigmoid(logits_shifted)
173
+
174
+ def get_sparsity(self) -> float:
175
+ """
176
+ Get the overall sparsity level (fraction of inactive gates).
177
+
178
+ Returns
179
+ -------
180
+ float
181
+ Sparsity level in [0, 1] where 1 means all gates are inactive
182
+ """
183
+ with torch.no_grad():
184
+ prob_active = self.get_active_prob()
185
+ return 1.0 - prob_active.mean().item()
186
+
187
+ @torch.no_grad()
188
+ def get_num_active(self) -> int:
189
+ """
190
+ Get the number of active gates (for monitoring).
191
+
192
+ Returns
193
+ -------
194
+ int
195
+ Number of gates that are likely active
196
+ """
197
+ prob_active = self.get_active_prob()
198
+ return int((prob_active > 0.5).sum().item())
199
+
200
+ def extra_repr(self) -> str:
201
+ """String representation for printing."""
202
+ return (
203
+ f"gate_size={self.gate_size}, temperature={self.temperature}, "
204
+ f"stretch={self.stretch}, init_mean={self.init_mean}"
205
+ )