oscura 0.0.1__py3-none-any.whl → 0.1.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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Workflow execution and DAG-based analysis.
|
|
2
|
+
|
|
3
|
+
This module provides directed acyclic graph (DAG) execution for complex
|
|
4
|
+
multi-step analysis workflows with automatic dependency resolution and
|
|
5
|
+
parallel execution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from oscura.workflow.dag import TaskNode, WorkflowDAG
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"TaskNode",
|
|
12
|
+
"WorkflowDAG",
|
|
13
|
+
]
|
oscura/workflow/dag.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Directed Acyclic Graph (DAG) execution for complex workflows.
|
|
2
|
+
|
|
3
|
+
This module provides DAG-based workflow execution with automatic dependency
|
|
4
|
+
resolution and parallel execution of independent tasks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict, deque
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from oscura.core.exceptions import AnalysisError
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class TaskNode:
|
|
22
|
+
"""Node in a workflow DAG.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
name: Unique task name.
|
|
26
|
+
func: Callable function to execute.
|
|
27
|
+
depends_on: List of task names this task depends on.
|
|
28
|
+
result: Computed result (set after execution).
|
|
29
|
+
completed: Whether task has been executed.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> def compute_fft(state):
|
|
33
|
+
... return {'fft': np.fft.fft(state['trace'])}
|
|
34
|
+
>>> node = TaskNode(name='fft', func=compute_fft, depends_on=['load'])
|
|
35
|
+
|
|
36
|
+
References:
|
|
37
|
+
API-013: DAG Execution
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
func: Callable[[dict[str, Any]], Any]
|
|
42
|
+
depends_on: list[str] = field(default_factory=list)
|
|
43
|
+
result: Any = None
|
|
44
|
+
completed: bool = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WorkflowDAG:
|
|
48
|
+
"""Directed Acyclic Graph for workflow execution.
|
|
49
|
+
|
|
50
|
+
Manages task dependencies and executes tasks in topological order
|
|
51
|
+
with automatic parallelization of independent tasks.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> from oscura.workflow.dag import WorkflowDAG
|
|
55
|
+
>>> dag = WorkflowDAG()
|
|
56
|
+
>>> dag.add_task('load', load_trace, depends_on=[])
|
|
57
|
+
>>> dag.add_task('fft', compute_fft, depends_on=['load'])
|
|
58
|
+
>>> dag.add_task('rise_time', compute_rise_time, depends_on=['load'])
|
|
59
|
+
>>> dag.add_task('enob', compute_enob, depends_on=['fft', 'rise_time'])
|
|
60
|
+
>>> results = dag.execute()
|
|
61
|
+
|
|
62
|
+
References:
|
|
63
|
+
API-013: DAG Execution for Complex Workflows
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
"""Initialize empty DAG."""
|
|
68
|
+
self.tasks: dict[str, TaskNode] = {}
|
|
69
|
+
self._adjacency: dict[str, list[str]] = defaultdict(list)
|
|
70
|
+
|
|
71
|
+
def add_task(
|
|
72
|
+
self,
|
|
73
|
+
name: str,
|
|
74
|
+
func: Callable[[dict[str, Any]], Any],
|
|
75
|
+
depends_on: list[str] | None = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Add a task to the DAG.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name: Unique name for the task.
|
|
81
|
+
func: Function to execute. Should accept state dict and return result.
|
|
82
|
+
depends_on: List of task names this task depends on.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
AnalysisError: If task name already exists or creates a cycle.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> dag.add_task('fft', compute_fft, depends_on=['load'])
|
|
89
|
+
|
|
90
|
+
References:
|
|
91
|
+
API-013: DAG Execution
|
|
92
|
+
"""
|
|
93
|
+
if name in self.tasks:
|
|
94
|
+
raise AnalysisError(f"Task '{name}' already exists in DAG")
|
|
95
|
+
|
|
96
|
+
depends_on = depends_on or []
|
|
97
|
+
|
|
98
|
+
# Verify dependencies exist
|
|
99
|
+
for dep in depends_on:
|
|
100
|
+
if dep not in self.tasks:
|
|
101
|
+
raise AnalysisError(f"Dependency '{dep}' not found for task '{name}'")
|
|
102
|
+
|
|
103
|
+
# Create task node
|
|
104
|
+
task = TaskNode(name=name, func=func, depends_on=depends_on)
|
|
105
|
+
self.tasks[name] = task
|
|
106
|
+
|
|
107
|
+
# Update adjacency list
|
|
108
|
+
for dep in depends_on:
|
|
109
|
+
self._adjacency[dep].append(name)
|
|
110
|
+
|
|
111
|
+
# Check for cycles
|
|
112
|
+
if self._has_cycle():
|
|
113
|
+
# Rollback - remove task
|
|
114
|
+
del self.tasks[name]
|
|
115
|
+
for dep in depends_on:
|
|
116
|
+
self._adjacency[dep].remove(name)
|
|
117
|
+
raise AnalysisError(f"Adding task '{name}' would create a cycle in DAG")
|
|
118
|
+
|
|
119
|
+
def _has_cycle(self) -> bool:
|
|
120
|
+
"""Check if DAG contains a cycle.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if cycle detected.
|
|
124
|
+
"""
|
|
125
|
+
# Use DFS to detect cycles
|
|
126
|
+
visited: set[str] = set()
|
|
127
|
+
rec_stack: set[str] = set()
|
|
128
|
+
|
|
129
|
+
def dfs(node: str) -> bool:
|
|
130
|
+
visited.add(node)
|
|
131
|
+
rec_stack.add(node)
|
|
132
|
+
|
|
133
|
+
for neighbor in self._adjacency.get(node, []):
|
|
134
|
+
if neighbor not in visited:
|
|
135
|
+
if dfs(neighbor):
|
|
136
|
+
return True
|
|
137
|
+
elif neighbor in rec_stack:
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
rec_stack.remove(node)
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
return any(task_name not in visited and dfs(task_name) for task_name in self.tasks)
|
|
144
|
+
|
|
145
|
+
def _topological_sort(self) -> list[list[str]]:
|
|
146
|
+
"""Compute topological sort grouped by execution level.
|
|
147
|
+
|
|
148
|
+
Tasks at the same level can be executed in parallel.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of levels, where each level is a list of task names.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
AnalysisError: If DAG contains a cycle or unreachable tasks.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> levels = dag._topological_sort()
|
|
158
|
+
>>> # [[load], [fft, rise_time], [enob]]
|
|
159
|
+
"""
|
|
160
|
+
# Compute in-degree for each node
|
|
161
|
+
in_degree = {name: len(task.depends_on) for name, task in self.tasks.items()}
|
|
162
|
+
|
|
163
|
+
# Find nodes with no dependencies (level 0)
|
|
164
|
+
levels: list[list[str]] = []
|
|
165
|
+
queue = deque([name for name, degree in in_degree.items() if degree == 0])
|
|
166
|
+
|
|
167
|
+
while queue:
|
|
168
|
+
# All tasks at this level can run in parallel
|
|
169
|
+
current_level = list(queue)
|
|
170
|
+
levels.append(current_level)
|
|
171
|
+
queue.clear()
|
|
172
|
+
|
|
173
|
+
# Process current level
|
|
174
|
+
for task_name in current_level:
|
|
175
|
+
# Reduce in-degree of dependent tasks
|
|
176
|
+
for dependent in self._adjacency.get(task_name, []):
|
|
177
|
+
in_degree[dependent] -= 1
|
|
178
|
+
if in_degree[dependent] == 0:
|
|
179
|
+
queue.append(dependent)
|
|
180
|
+
|
|
181
|
+
# Verify all tasks were included
|
|
182
|
+
total_tasks = sum(len(level) for level in levels)
|
|
183
|
+
if total_tasks != len(self.tasks):
|
|
184
|
+
raise AnalysisError("DAG contains a cycle or unreachable tasks")
|
|
185
|
+
|
|
186
|
+
return levels
|
|
187
|
+
|
|
188
|
+
def execute(
|
|
189
|
+
self,
|
|
190
|
+
*,
|
|
191
|
+
initial_state: dict[str, Any] | None = None,
|
|
192
|
+
parallel: bool = True,
|
|
193
|
+
max_workers: int | None = None,
|
|
194
|
+
) -> dict[str, Any]:
|
|
195
|
+
"""Execute the workflow DAG.
|
|
196
|
+
|
|
197
|
+
Tasks are executed in topological order with automatic parallelization
|
|
198
|
+
of independent tasks at each level.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
initial_state: Initial state dictionary passed to first tasks.
|
|
202
|
+
parallel: Enable parallel execution of independent tasks.
|
|
203
|
+
max_workers: Maximum number of parallel workers. None uses CPU count.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Final state dictionary containing all task results.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> results = dag.execute(initial_state={'trace': trace_data})
|
|
210
|
+
>>> print(results['enob'])
|
|
211
|
+
|
|
212
|
+
References:
|
|
213
|
+
API-013: DAG Execution
|
|
214
|
+
"""
|
|
215
|
+
if not self.tasks:
|
|
216
|
+
return initial_state or {}
|
|
217
|
+
|
|
218
|
+
state = initial_state or {}
|
|
219
|
+
levels = self._topological_sort()
|
|
220
|
+
|
|
221
|
+
for level in levels:
|
|
222
|
+
if parallel and len(level) > 1:
|
|
223
|
+
# Execute level in parallel
|
|
224
|
+
self._execute_level_parallel(level, state, max_workers)
|
|
225
|
+
else:
|
|
226
|
+
# Execute level sequentially
|
|
227
|
+
self._execute_level_sequential(level, state)
|
|
228
|
+
|
|
229
|
+
return state
|
|
230
|
+
|
|
231
|
+
def _execute_level_sequential(self, level: list[str], state: dict[str, Any]) -> None:
|
|
232
|
+
"""Execute a level of tasks sequentially.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
level: List of task names to execute.
|
|
236
|
+
state: Shared state dictionary.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
AnalysisError: If task execution fails.
|
|
240
|
+
"""
|
|
241
|
+
for task_name in level:
|
|
242
|
+
task = self.tasks[task_name]
|
|
243
|
+
try:
|
|
244
|
+
result = task.func(state)
|
|
245
|
+
task.result = result
|
|
246
|
+
task.completed = True
|
|
247
|
+
|
|
248
|
+
# Update state with result
|
|
249
|
+
if isinstance(result, dict):
|
|
250
|
+
state.update(result)
|
|
251
|
+
else:
|
|
252
|
+
state[task_name] = result
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
raise AnalysisError(f"Task '{task_name}' failed: {e}") from e
|
|
256
|
+
|
|
257
|
+
def _execute_level_parallel(
|
|
258
|
+
self, level: list[str], state: dict[str, Any], max_workers: int | None
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Execute a level of tasks in parallel.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
level: List of task names to execute in parallel.
|
|
264
|
+
state: Shared state dictionary.
|
|
265
|
+
max_workers: Maximum number of workers.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
AnalysisError: If task execution fails.
|
|
269
|
+
"""
|
|
270
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
271
|
+
# Submit all tasks
|
|
272
|
+
future_to_task = {executor.submit(self.tasks[name].func, state): name for name in level}
|
|
273
|
+
|
|
274
|
+
# Collect results
|
|
275
|
+
for future in as_completed(future_to_task):
|
|
276
|
+
task_name = future_to_task[future]
|
|
277
|
+
task = self.tasks[task_name]
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
result = future.result()
|
|
281
|
+
task.result = result
|
|
282
|
+
task.completed = True
|
|
283
|
+
|
|
284
|
+
# Update state with result
|
|
285
|
+
if isinstance(result, dict):
|
|
286
|
+
state.update(result)
|
|
287
|
+
else:
|
|
288
|
+
state[task_name] = result
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
raise AnalysisError(f"Task '{task_name}' failed: {e}") from e
|
|
292
|
+
|
|
293
|
+
def get_result(self, task_name: str) -> Any:
|
|
294
|
+
"""Get result from a completed task.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
task_name: Name of the task.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Task result.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
AnalysisError: If task doesn't exist or hasn't been executed.
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
>>> fft_result = dag.get_result('fft')
|
|
307
|
+
"""
|
|
308
|
+
if task_name not in self.tasks:
|
|
309
|
+
raise AnalysisError(f"Task '{task_name}' not found in DAG")
|
|
310
|
+
|
|
311
|
+
task = self.tasks[task_name]
|
|
312
|
+
if not task.completed:
|
|
313
|
+
raise AnalysisError(f"Task '{task_name}' has not been executed yet")
|
|
314
|
+
|
|
315
|
+
return task.result
|
|
316
|
+
|
|
317
|
+
def reset(self) -> None:
|
|
318
|
+
"""Reset all task completion states.
|
|
319
|
+
|
|
320
|
+
Allows re-execution of the DAG with different initial state.
|
|
321
|
+
|
|
322
|
+
Example:
|
|
323
|
+
>>> dag.reset()
|
|
324
|
+
>>> results = dag.execute(initial_state={'trace': new_trace})
|
|
325
|
+
"""
|
|
326
|
+
for task in self.tasks.values():
|
|
327
|
+
task.completed = False
|
|
328
|
+
task.result = None
|
|
329
|
+
|
|
330
|
+
def to_graphviz(self) -> str:
|
|
331
|
+
"""Generate Graphviz DOT representation of the DAG.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
DOT format string for visualization.
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
>>> dot = dag.to_graphviz()
|
|
338
|
+
>>> with open('workflow.dot', 'w') as f:
|
|
339
|
+
... f.write(dot)
|
|
340
|
+
>>> # Then: dot -Tpng workflow.dot -o workflow.png
|
|
341
|
+
|
|
342
|
+
References:
|
|
343
|
+
API-013: DAG Execution
|
|
344
|
+
"""
|
|
345
|
+
lines = ["digraph WorkflowDAG {", " rankdir=LR;", " node [shape=box];", ""]
|
|
346
|
+
|
|
347
|
+
# Add nodes
|
|
348
|
+
for task_name, task in self.tasks.items():
|
|
349
|
+
style = "filled,bold" if task.completed else "filled"
|
|
350
|
+
color = "lightgreen" if task.completed else "lightblue"
|
|
351
|
+
lines.append(f' "{task_name}" [style="{style}", fillcolor="{color}"];')
|
|
352
|
+
|
|
353
|
+
lines.append("")
|
|
354
|
+
|
|
355
|
+
# Add edges
|
|
356
|
+
for task_name, task in self.tasks.items():
|
|
357
|
+
for dep in task.depends_on:
|
|
358
|
+
lines.append(f' "{dep}" -> "{task_name}";')
|
|
359
|
+
|
|
360
|
+
lines.append("}")
|
|
361
|
+
return "\n".join(lines)
|
|
362
|
+
|
|
363
|
+
def __repr__(self) -> str:
|
|
364
|
+
"""String representation of DAG."""
|
|
365
|
+
return f"WorkflowDAG(tasks={len(self.tasks)})"
|
|
366
|
+
|
|
367
|
+
def __str__(self) -> str:
|
|
368
|
+
"""Detailed string representation."""
|
|
369
|
+
lines = [f"WorkflowDAG with {len(self.tasks)} tasks:"]
|
|
370
|
+
for task_name, task in self.tasks.items():
|
|
371
|
+
deps = ", ".join(task.depends_on) if task.depends_on else "none"
|
|
372
|
+
status = "✓" if task.completed else "○"
|
|
373
|
+
lines.append(f" {status} {task_name} (depends on: {deps})")
|
|
374
|
+
return "\n".join(lines)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
__all__ = ["TaskNode", "WorkflowDAG"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""High-level workflow presets for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides one-call analysis workflows for common signal
|
|
4
|
+
characterization tasks.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> import oscura as tk
|
|
8
|
+
>>> # Reverse engineer unknown signal
|
|
9
|
+
>>> result = tk.workflows.reverse_engineer_signal(trace)
|
|
10
|
+
>>> print(result.protocol_spec)
|
|
11
|
+
>>>
|
|
12
|
+
>>> # EMC compliance testing
|
|
13
|
+
>>> results = tk.workflows.emc_compliance_test(trace)
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Multi-trace analysis
|
|
16
|
+
>>> stats = tk.workflows.load_all(["trace1.wfm", "trace2.wfm"])
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from oscura.workflows.compliance import emc_compliance_test
|
|
20
|
+
from oscura.workflows.digital import characterize_buffer
|
|
21
|
+
from oscura.workflows.multi_trace import (
|
|
22
|
+
AlignmentMethod,
|
|
23
|
+
MultiTraceResults,
|
|
24
|
+
MultiTraceWorkflow,
|
|
25
|
+
TraceStatistics,
|
|
26
|
+
load_all,
|
|
27
|
+
)
|
|
28
|
+
from oscura.workflows.power import power_analysis
|
|
29
|
+
from oscura.workflows.protocol import debug_protocol
|
|
30
|
+
from oscura.workflows.reverse_engineering import (
|
|
31
|
+
FieldSpec,
|
|
32
|
+
InferredFrame,
|
|
33
|
+
ProtocolSpec,
|
|
34
|
+
ReverseEngineeringResult,
|
|
35
|
+
reverse_engineer_signal,
|
|
36
|
+
)
|
|
37
|
+
from oscura.workflows.signal_integrity import signal_integrity_audit
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Multi-trace
|
|
41
|
+
"AlignmentMethod",
|
|
42
|
+
# Reverse engineering
|
|
43
|
+
"FieldSpec",
|
|
44
|
+
"InferredFrame",
|
|
45
|
+
"MultiTraceResults",
|
|
46
|
+
"MultiTraceWorkflow",
|
|
47
|
+
"ProtocolSpec",
|
|
48
|
+
"ReverseEngineeringResult",
|
|
49
|
+
"TraceStatistics",
|
|
50
|
+
# Domain workflows
|
|
51
|
+
"characterize_buffer",
|
|
52
|
+
"debug_protocol",
|
|
53
|
+
"emc_compliance_test",
|
|
54
|
+
"load_all",
|
|
55
|
+
"power_analysis",
|
|
56
|
+
"reverse_engineer_signal",
|
|
57
|
+
"signal_integrity_audit",
|
|
58
|
+
]
|