oscura 0.7.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
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.
- oscura/__init__.py +19 -19
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +102 -17
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
"""Parallel pipeline execution with automatic dependency analysis.
|
|
2
|
-
|
|
3
|
-
This module provides a parallel-capable Pipeline that analyzes stage dependencies
|
|
4
|
-
and executes independent stages concurrently using thread or process pools.
|
|
5
|
-
|
|
6
|
-
The ParallelPipeline maintains full API compatibility with the standard Pipeline
|
|
7
|
-
while providing linear speedup for independent transformations.
|
|
8
|
-
|
|
9
|
-
Example:
|
|
10
|
-
>>> from oscura.utils.pipeline import ParallelPipeline
|
|
11
|
-
>>> # Create pipeline with independent branches
|
|
12
|
-
>>> pipeline = ParallelPipeline([
|
|
13
|
-
... ('filter1', LowPassFilter(cutoff=1e6)),
|
|
14
|
-
... ('filter2', HighPassFilter(cutoff=1e5)),
|
|
15
|
-
... ('merge', MergeTransformer())
|
|
16
|
-
... ])
|
|
17
|
-
>>> # Independent filters run in parallel
|
|
18
|
-
>>> result = pipeline.transform(trace)
|
|
19
|
-
|
|
20
|
-
Performance:
|
|
21
|
-
For N independent stages, ParallelPipeline provides up to Nx speedup
|
|
22
|
-
compared to sequential execution. Overhead is ~10ms for thread pool,
|
|
23
|
-
~50ms for process pool.
|
|
24
|
-
|
|
25
|
-
References:
|
|
26
|
-
IEEE 1057-2017: Parallel processing for digitizer characterization
|
|
27
|
-
sklearn.pipeline.Pipeline: Sequential pipeline pattern
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
from __future__ import annotations
|
|
31
|
-
|
|
32
|
-
import os
|
|
33
|
-
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
|
|
34
|
-
from typing import TYPE_CHECKING, Literal
|
|
35
|
-
|
|
36
|
-
from .pipeline import Pipeline
|
|
37
|
-
|
|
38
|
-
if TYPE_CHECKING:
|
|
39
|
-
from collections.abc import Sequence
|
|
40
|
-
|
|
41
|
-
from oscura.core.types import WaveformTrace
|
|
42
|
-
|
|
43
|
-
from .base import TraceTransformer
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class ParallelPipeline(Pipeline):
|
|
47
|
-
"""Pipeline with parallel execution of independent stages.
|
|
48
|
-
|
|
49
|
-
Analyzes dependencies between pipeline stages and executes independent
|
|
50
|
-
stages concurrently. Automatically determines dependency graph from
|
|
51
|
-
stage inputs/outputs.
|
|
52
|
-
|
|
53
|
-
Stages are independent if they:
|
|
54
|
-
1. Don't modify shared state
|
|
55
|
-
2. Only depend on the input trace (not other stage outputs)
|
|
56
|
-
3. Can be executed in any order
|
|
57
|
-
|
|
58
|
-
Attributes:
|
|
59
|
-
steps: List of (name, transformer) tuples defining the pipeline stages.
|
|
60
|
-
named_steps: Dictionary mapping step names to transformers.
|
|
61
|
-
executor_type: Type of executor ('thread' or 'process').
|
|
62
|
-
max_workers: Maximum number of parallel workers (None = auto).
|
|
63
|
-
|
|
64
|
-
Example - Independent Filters:
|
|
65
|
-
>>> # These filters are independent - they both take the input trace
|
|
66
|
-
>>> pipeline = ParallelPipeline([
|
|
67
|
-
... ('lowpass', LowPassFilter(cutoff=1e6)),
|
|
68
|
-
... ('highpass', HighPassFilter(cutoff=1e5)),
|
|
69
|
-
... ('bandpass', BandPassFilter(low=1e5, high=1e6)),
|
|
70
|
-
... ], executor_type='thread', max_workers=3)
|
|
71
|
-
>>> result = pipeline.transform(trace) # All 3 run in parallel
|
|
72
|
-
|
|
73
|
-
Example - Mixed Sequential and Parallel:
|
|
74
|
-
>>> # First stage sequential, then parallel analysis
|
|
75
|
-
>>> pipeline = ParallelPipeline([
|
|
76
|
-
... ('preprocess', Normalize()), # Sequential
|
|
77
|
-
... ('fft', FFT()), # Parallel (from preprocessed)
|
|
78
|
-
... ('wavelet', WaveletTransform()), # Parallel (from preprocessed)
|
|
79
|
-
... ('merge', CombineResults()) # Sequential (waits for fft+wavelet)
|
|
80
|
-
... ])
|
|
81
|
-
|
|
82
|
-
Example - Process Pool for CPU-Intensive Tasks:
|
|
83
|
-
>>> # Use process pool for heavy computation
|
|
84
|
-
>>> pipeline = ParallelPipeline([
|
|
85
|
-
... ('fft1', FFT(nfft=65536)),
|
|
86
|
-
... ('fft2', FFT(nfft=32768)),
|
|
87
|
-
... ('fft3', FFT(nfft=16384)),
|
|
88
|
-
... ], executor_type='process', max_workers=None) # Auto worker count
|
|
89
|
-
|
|
90
|
-
Performance Characteristics:
|
|
91
|
-
Thread pool:
|
|
92
|
-
- Best for I/O-bound tasks (file loading, network)
|
|
93
|
-
- Low overhead (~10ms startup)
|
|
94
|
-
- Shared memory (no serialization)
|
|
95
|
-
- Limited by GIL for CPU-bound tasks
|
|
96
|
-
|
|
97
|
-
Process pool:
|
|
98
|
-
- Best for CPU-bound tasks (FFT, filtering, analysis)
|
|
99
|
-
- Higher overhead (~50ms startup)
|
|
100
|
-
- Requires picklable transformers
|
|
101
|
-
- True parallelism (bypasses GIL)
|
|
102
|
-
|
|
103
|
-
References:
|
|
104
|
-
concurrent.futures.ThreadPoolExecutor
|
|
105
|
-
concurrent.futures.ProcessPoolExecutor
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
def __init__(
|
|
109
|
-
self,
|
|
110
|
-
steps: Sequence[tuple[str, TraceTransformer]],
|
|
111
|
-
executor_type: Literal["thread", "process"] = "thread",
|
|
112
|
-
max_workers: int | None = None,
|
|
113
|
-
) -> None:
|
|
114
|
-
"""Initialize parallel pipeline with executor configuration.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
steps: Sequence of (name, transformer) tuples. Each transformer
|
|
118
|
-
must be a TraceTransformer instance.
|
|
119
|
-
executor_type: Type of executor to use:
|
|
120
|
-
- 'thread': ThreadPoolExecutor (default, low overhead)
|
|
121
|
-
- 'process': ProcessPoolExecutor (true parallelism, higher overhead)
|
|
122
|
-
max_workers: Maximum number of parallel workers. If None, uses
|
|
123
|
-
automatic selection:
|
|
124
|
-
- Thread pool: min(32, num_cpu + 4)
|
|
125
|
-
- Process pool: num_cpu
|
|
126
|
-
|
|
127
|
-
Raises:
|
|
128
|
-
ValueError: If step names are not unique, empty, or executor_type invalid.
|
|
129
|
-
|
|
130
|
-
Example:
|
|
131
|
-
>>> # Auto worker count (recommended)
|
|
132
|
-
>>> pipeline = ParallelPipeline([
|
|
133
|
-
... ('stage1', Transformer1()),
|
|
134
|
-
... ('stage2', Transformer2())
|
|
135
|
-
... ])
|
|
136
|
-
>>> # Explicit worker count
|
|
137
|
-
>>> pipeline = ParallelPipeline([
|
|
138
|
-
... ('stage1', Transformer1()),
|
|
139
|
-
... ('stage2', Transformer2())
|
|
140
|
-
... ], max_workers=4)
|
|
141
|
-
"""
|
|
142
|
-
# Initialize parent Pipeline
|
|
143
|
-
super().__init__(steps)
|
|
144
|
-
|
|
145
|
-
# Validate executor type
|
|
146
|
-
if executor_type not in ("thread", "process"):
|
|
147
|
-
raise ValueError(f"executor_type must be 'thread' or 'process', got '{executor_type}'")
|
|
148
|
-
|
|
149
|
-
self.executor_type = executor_type
|
|
150
|
-
self.max_workers = max_workers
|
|
151
|
-
self._dependency_graph: dict[str, list[str]] = {}
|
|
152
|
-
self._execution_order: list[list[str]] = []
|
|
153
|
-
|
|
154
|
-
# Analyze dependencies on initialization
|
|
155
|
-
self._analyze_dependencies()
|
|
156
|
-
|
|
157
|
-
def _analyze_dependencies(self) -> None:
|
|
158
|
-
"""Analyze dependencies between pipeline stages.
|
|
159
|
-
|
|
160
|
-
Builds a dependency graph by examining which stages depend on outputs
|
|
161
|
-
from other stages. Currently uses a conservative approach:
|
|
162
|
-
|
|
163
|
-
- First stage has no dependencies
|
|
164
|
-
- All other stages depend on the previous stage (sequential)
|
|
165
|
-
- Future enhancement: analyze transformer inputs to detect true dependencies
|
|
166
|
-
|
|
167
|
-
This conservative approach ensures correctness while still allowing
|
|
168
|
-
parallel execution when stages are explicitly independent.
|
|
169
|
-
|
|
170
|
-
The dependency analysis produces an execution order as a list of
|
|
171
|
-
"generations" (lists of stage names). Stages in the same generation
|
|
172
|
-
can be executed in parallel.
|
|
173
|
-
|
|
174
|
-
Example:
|
|
175
|
-
For pipeline: [filter1, filter2, merge]
|
|
176
|
-
If all stages are sequential:
|
|
177
|
-
execution_order = [['filter1'], ['filter2'], ['merge']]
|
|
178
|
-
If filter1 and filter2 are independent:
|
|
179
|
-
execution_order = [['filter1', 'filter2'], ['merge']]
|
|
180
|
-
|
|
181
|
-
NOTE: Current implementation is conservative and assumes sequential
|
|
182
|
-
dependencies. Future versions will support dependency hints via
|
|
183
|
-
transformer metadata to enable automatic parallelization.
|
|
184
|
-
|
|
185
|
-
References:
|
|
186
|
-
FUTURE-002: Advanced dependency analysis
|
|
187
|
-
"""
|
|
188
|
-
# Build dependency graph (conservative: each stage depends on previous)
|
|
189
|
-
self._dependency_graph = {}
|
|
190
|
-
|
|
191
|
-
for i, (name, _transformer) in enumerate(self.steps):
|
|
192
|
-
if i == 0:
|
|
193
|
-
# First stage has no dependencies
|
|
194
|
-
self._dependency_graph[name] = []
|
|
195
|
-
else:
|
|
196
|
-
# Each stage depends on the previous stage
|
|
197
|
-
prev_name = self.steps[i - 1][0]
|
|
198
|
-
self._dependency_graph[name] = [prev_name]
|
|
199
|
-
|
|
200
|
-
# Build execution order (topological sort by generations)
|
|
201
|
-
self._execution_order = self._compute_execution_order()
|
|
202
|
-
|
|
203
|
-
def _compute_execution_order(self) -> list[list[str]]:
|
|
204
|
-
"""Compute execution order as list of parallel generations.
|
|
205
|
-
|
|
206
|
-
Uses topological sort to group stages into generations where each
|
|
207
|
-
generation contains stages that can execute in parallel.
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
List of generations, where each generation is a list of stage names
|
|
211
|
-
that can execute in parallel.
|
|
212
|
-
|
|
213
|
-
Raises:
|
|
214
|
-
ValueError: If circular dependencies detected.
|
|
215
|
-
|
|
216
|
-
Example:
|
|
217
|
-
For graph: {A: [], B: [A], C: [A], D: [B, C]}
|
|
218
|
-
Returns: [[A], [B, C], [D]]
|
|
219
|
-
"""
|
|
220
|
-
# Copy dependency graph (we'll modify it)
|
|
221
|
-
deps = {name: set(deps) for name, deps in self._dependency_graph.items()}
|
|
222
|
-
generations: list[list[str]] = []
|
|
223
|
-
|
|
224
|
-
# All stage names
|
|
225
|
-
all_stages = set(self._dependency_graph.keys())
|
|
226
|
-
completed: set[str] = set()
|
|
227
|
-
|
|
228
|
-
# Build generations
|
|
229
|
-
while completed != all_stages:
|
|
230
|
-
# Find all stages with no remaining dependencies
|
|
231
|
-
ready = [name for name in all_stages - completed if not deps[name]]
|
|
232
|
-
|
|
233
|
-
if not ready:
|
|
234
|
-
# Cycle detected (shouldn't happen with valid pipeline)
|
|
235
|
-
raise ValueError("Circular dependency detected in pipeline")
|
|
236
|
-
|
|
237
|
-
generations.append(ready)
|
|
238
|
-
completed.update(ready)
|
|
239
|
-
|
|
240
|
-
# Remove completed stages from dependencies
|
|
241
|
-
for name in all_stages - completed:
|
|
242
|
-
deps[name] -= set(ready)
|
|
243
|
-
|
|
244
|
-
return generations
|
|
245
|
-
|
|
246
|
-
def _get_max_workers(self) -> int:
|
|
247
|
-
"""Get the maximum number of workers to use.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
Number of workers, using automatic selection if max_workers is None.
|
|
251
|
-
|
|
252
|
-
Example:
|
|
253
|
-
>>> pipeline._get_max_workers()
|
|
254
|
-
8 # On 8-core machine with thread executor
|
|
255
|
-
"""
|
|
256
|
-
if self.max_workers is not None:
|
|
257
|
-
return self.max_workers
|
|
258
|
-
|
|
259
|
-
# Automatic worker count selection
|
|
260
|
-
cpu_count = os.cpu_count() or 4
|
|
261
|
-
|
|
262
|
-
if self.executor_type == "thread":
|
|
263
|
-
# Thread pool: min(32, cpu_count + 4) - default from ThreadPoolExecutor
|
|
264
|
-
return min(32, cpu_count + 4)
|
|
265
|
-
else:
|
|
266
|
-
# Process pool: cpu_count - default from ProcessPoolExecutor
|
|
267
|
-
return cpu_count
|
|
268
|
-
|
|
269
|
-
def transform(self, trace: WaveformTrace) -> WaveformTrace:
|
|
270
|
-
"""Transform trace through pipeline with parallel execution.
|
|
271
|
-
|
|
272
|
-
Executes stages in parallel when possible, according to the dependency
|
|
273
|
-
graph. Stages in the same generation run concurrently.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
trace: Input WaveformTrace to transform.
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
Transformed WaveformTrace after passing through all stages.
|
|
280
|
-
|
|
281
|
-
Example:
|
|
282
|
-
>>> result = pipeline.transform(trace)
|
|
283
|
-
|
|
284
|
-
Performance:
|
|
285
|
-
For N independent stages with T execution time each:
|
|
286
|
-
- Sequential: N * T
|
|
287
|
-
- Parallel: T + overhead (~10-50ms)
|
|
288
|
-
- Speedup: ~Nx (minus overhead)
|
|
289
|
-
"""
|
|
290
|
-
current = trace
|
|
291
|
-
self._intermediate_results.clear()
|
|
292
|
-
|
|
293
|
-
# Choose executor based on configuration
|
|
294
|
-
executor_class = (
|
|
295
|
-
ThreadPoolExecutor if self.executor_type == "thread" else ProcessPoolExecutor
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
with executor_class(max_workers=self._get_max_workers()) as executor:
|
|
299
|
-
# Execute each generation in parallel
|
|
300
|
-
for generation in self._execution_order:
|
|
301
|
-
if len(generation) == 1:
|
|
302
|
-
# Single stage - execute directly (no overhead)
|
|
303
|
-
name = generation[0]
|
|
304
|
-
transformer = self.named_steps[name]
|
|
305
|
-
current = transformer.transform(current)
|
|
306
|
-
self._intermediate_results[name] = current
|
|
307
|
-
else:
|
|
308
|
-
# Multiple stages - execute in parallel
|
|
309
|
-
futures = {}
|
|
310
|
-
for name in generation:
|
|
311
|
-
transformer = self.named_steps[name]
|
|
312
|
-
future = executor.submit(transformer.transform, current)
|
|
313
|
-
futures[name] = future
|
|
314
|
-
|
|
315
|
-
# Collect results
|
|
316
|
-
results = {}
|
|
317
|
-
for name, future in futures.items():
|
|
318
|
-
result = future.result()
|
|
319
|
-
results[name] = result
|
|
320
|
-
self._intermediate_results[name] = result
|
|
321
|
-
|
|
322
|
-
# For conservative sequential execution, current is the last result
|
|
323
|
-
# For true parallel execution with merge, this would be handled differently
|
|
324
|
-
current = results[generation[-1]]
|
|
325
|
-
|
|
326
|
-
return current
|
|
327
|
-
|
|
328
|
-
def fit(self, trace: WaveformTrace) -> ParallelPipeline:
|
|
329
|
-
"""Fit all transformers in the pipeline.
|
|
330
|
-
|
|
331
|
-
Fits each transformer sequentially on the output of the previous stage.
|
|
332
|
-
Fitting is always sequential because it may modify transformer state.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
trace: Reference WaveformTrace to fit to.
|
|
336
|
-
|
|
337
|
-
Returns:
|
|
338
|
-
Self for method chaining.
|
|
339
|
-
|
|
340
|
-
Example:
|
|
341
|
-
>>> pipeline = ParallelPipeline([
|
|
342
|
-
... ('normalize', AdaptiveNormalizer()),
|
|
343
|
-
... ('filter', AdaptiveFilter())
|
|
344
|
-
... ])
|
|
345
|
-
>>> pipeline.fit(reference_trace)
|
|
346
|
-
|
|
347
|
-
NOTE: fit() is always sequential because transformers may have
|
|
348
|
-
interdependent learned parameters. Use transform() for parallel execution.
|
|
349
|
-
"""
|
|
350
|
-
# Fitting is always sequential - reuse parent implementation
|
|
351
|
-
super().fit(trace)
|
|
352
|
-
return self
|
|
353
|
-
|
|
354
|
-
def clone(self) -> ParallelPipeline:
|
|
355
|
-
"""Create a copy of this parallel pipeline.
|
|
356
|
-
|
|
357
|
-
Returns:
|
|
358
|
-
New ParallelPipeline instance with cloned transformers and same configuration.
|
|
359
|
-
|
|
360
|
-
Example:
|
|
361
|
-
>>> pipeline_copy = pipeline.clone()
|
|
362
|
-
"""
|
|
363
|
-
cloned_steps = [(name, transformer.clone()) for name, transformer in self.steps]
|
|
364
|
-
return ParallelPipeline(
|
|
365
|
-
cloned_steps, executor_type=self.executor_type, max_workers=self.max_workers
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
def get_dependency_graph(self) -> dict[str, list[str]]:
|
|
369
|
-
"""Get the dependency graph for pipeline stages.
|
|
370
|
-
|
|
371
|
-
Returns:
|
|
372
|
-
Dictionary mapping stage names to list of dependency stage names.
|
|
373
|
-
|
|
374
|
-
Example:
|
|
375
|
-
>>> graph = pipeline.get_dependency_graph()
|
|
376
|
-
>>> print(graph)
|
|
377
|
-
{'filter1': [], 'filter2': ['filter1'], 'merge': ['filter2']}
|
|
378
|
-
|
|
379
|
-
References:
|
|
380
|
-
API-006: Pipeline Dependency Analysis
|
|
381
|
-
"""
|
|
382
|
-
return self._dependency_graph.copy()
|
|
383
|
-
|
|
384
|
-
def get_execution_order(self) -> list[list[str]]:
|
|
385
|
-
"""Get the execution order as parallel generations.
|
|
386
|
-
|
|
387
|
-
Returns:
|
|
388
|
-
List of generations, where each generation is a list of stage names
|
|
389
|
-
that will execute in parallel.
|
|
390
|
-
|
|
391
|
-
Example:
|
|
392
|
-
>>> order = pipeline.get_execution_order()
|
|
393
|
-
>>> print(order)
|
|
394
|
-
[['filter1', 'filter2'], ['merge']]
|
|
395
|
-
>>> # filter1 and filter2 run in parallel, then merge
|
|
396
|
-
|
|
397
|
-
References:
|
|
398
|
-
API-006: Pipeline Dependency Analysis
|
|
399
|
-
"""
|
|
400
|
-
return [gen.copy() for gen in self._execution_order]
|
|
401
|
-
|
|
402
|
-
def set_parallel_config(
|
|
403
|
-
self,
|
|
404
|
-
executor_type: Literal["thread", "process"] | None = None,
|
|
405
|
-
max_workers: int | None = None,
|
|
406
|
-
) -> ParallelPipeline:
|
|
407
|
-
"""Update parallel execution configuration.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
executor_type: New executor type ('thread' or 'process'). If None, keeps current.
|
|
411
|
-
max_workers: New max worker count. If None, keeps current (may be auto).
|
|
412
|
-
|
|
413
|
-
Returns:
|
|
414
|
-
Self for method chaining.
|
|
415
|
-
|
|
416
|
-
Raises:
|
|
417
|
-
ValueError: If executor_type is invalid.
|
|
418
|
-
|
|
419
|
-
Example:
|
|
420
|
-
>>> # Switch to process pool with 4 workers
|
|
421
|
-
>>> pipeline.set_parallel_config(executor_type='process', max_workers=4)
|
|
422
|
-
>>> # Switch to auto worker count
|
|
423
|
-
>>> pipeline.set_parallel_config(max_workers=None)
|
|
424
|
-
|
|
425
|
-
References:
|
|
426
|
-
API-006: Pipeline Parallel Configuration
|
|
427
|
-
"""
|
|
428
|
-
if executor_type is not None:
|
|
429
|
-
if executor_type not in ("thread", "process"):
|
|
430
|
-
raise ValueError(
|
|
431
|
-
f"executor_type must be 'thread' or 'process', got '{executor_type}'"
|
|
432
|
-
)
|
|
433
|
-
self.executor_type = executor_type
|
|
434
|
-
|
|
435
|
-
if max_workers is not None:
|
|
436
|
-
self.max_workers = max_workers
|
|
437
|
-
|
|
438
|
-
return self
|
|
439
|
-
|
|
440
|
-
def __repr__(self) -> str:
|
|
441
|
-
"""String representation of the parallel pipeline."""
|
|
442
|
-
step_strs = [
|
|
443
|
-
f"('{name}', {transformer.__class__.__name__})" for name, transformer in self.steps
|
|
444
|
-
]
|
|
445
|
-
config_str = f"executor={self.executor_type}, workers={self.max_workers or 'auto'}"
|
|
446
|
-
return "ParallelPipeline([\n " + ",\n ".join(step_strs) + f"\n], {config_str})"
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
__all__ = ["ParallelPipeline"]
|