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,391 @@
|
|
|
1
|
+
"""Limit testing for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides specification limit testing including upper/lower
|
|
4
|
+
bounds, pass/fail determination, and margin analysis.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.comparison import check_limits, margin_analysis
|
|
9
|
+
>>> result = check_limits(trace, upper=1.5, lower=-0.5)
|
|
10
|
+
>>> margins = margin_analysis(trace, limits)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from oscura.core.exceptions import AnalysisError
|
|
21
|
+
from oscura.core.types import WaveformTrace
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from numpy.typing import NDArray
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class LimitSpec:
|
|
29
|
+
"""Specification limit definition.
|
|
30
|
+
|
|
31
|
+
Defines upper and lower limits for a measurement with optional
|
|
32
|
+
guardbands and absolute/relative modes.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
upper: Upper limit value.
|
|
36
|
+
lower: Lower limit value.
|
|
37
|
+
upper_guardband: Guardband below upper limit (margin).
|
|
38
|
+
lower_guardband: Guardband above lower limit (margin).
|
|
39
|
+
name: Name of the specification.
|
|
40
|
+
unit: Unit of measurement.
|
|
41
|
+
mode: Limit mode ("absolute" or "relative").
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
upper: float | None = None
|
|
45
|
+
lower: float | None = None
|
|
46
|
+
upper_guardband: float = 0.0
|
|
47
|
+
lower_guardband: float = 0.0
|
|
48
|
+
name: str = "spec"
|
|
49
|
+
unit: str = ""
|
|
50
|
+
mode: Literal["absolute", "relative"] = "absolute"
|
|
51
|
+
|
|
52
|
+
def __post_init__(self) -> None:
|
|
53
|
+
"""Validate limit specification."""
|
|
54
|
+
if self.upper is None and self.lower is None:
|
|
55
|
+
raise ValueError("At least one of upper or lower limit must be specified")
|
|
56
|
+
if self.upper is not None and self.lower is not None and self.upper < self.lower:
|
|
57
|
+
raise ValueError(f"Upper limit ({self.upper}) must be >= lower limit ({self.lower})")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class LimitTestResult:
|
|
62
|
+
"""Result of a limit test.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
passed: True if all samples are within limits.
|
|
66
|
+
num_violations: Number of samples violating limits.
|
|
67
|
+
violation_rate: Fraction of samples violating limits.
|
|
68
|
+
upper_violations: Indices of samples exceeding upper limit.
|
|
69
|
+
lower_violations: Indices of samples below lower limit.
|
|
70
|
+
max_value: Maximum value in data.
|
|
71
|
+
min_value: Minimum value in data.
|
|
72
|
+
upper_margin: Margin to upper limit (positive = within, negative = exceeded).
|
|
73
|
+
lower_margin: Margin to lower limit (positive = within, negative = exceeded).
|
|
74
|
+
margin_percentage: Smallest margin as percentage of limit range.
|
|
75
|
+
within_guardband: True if within guardband but outside tight limits.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
passed: bool
|
|
79
|
+
num_violations: int
|
|
80
|
+
violation_rate: float
|
|
81
|
+
upper_violations: NDArray[np.int64] | None = None
|
|
82
|
+
lower_violations: NDArray[np.int64] | None = None
|
|
83
|
+
max_value: float = 0.0
|
|
84
|
+
min_value: float = 0.0
|
|
85
|
+
upper_margin: float | None = None
|
|
86
|
+
lower_margin: float | None = None
|
|
87
|
+
margin_percentage: float | None = None
|
|
88
|
+
within_guardband: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def create_limit_spec(
|
|
92
|
+
*,
|
|
93
|
+
upper: float | None = None,
|
|
94
|
+
lower: float | None = None,
|
|
95
|
+
center: float | None = None,
|
|
96
|
+
tolerance: float | None = None,
|
|
97
|
+
tolerance_pct: float | None = None,
|
|
98
|
+
guardband_pct: float = 0.0,
|
|
99
|
+
name: str = "spec",
|
|
100
|
+
unit: str = "",
|
|
101
|
+
) -> LimitSpec:
|
|
102
|
+
"""Create a limit specification.
|
|
103
|
+
|
|
104
|
+
Creates a LimitSpec from various input formats including
|
|
105
|
+
center +/- tolerance notation.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
upper: Upper limit value.
|
|
109
|
+
lower: Lower limit value.
|
|
110
|
+
center: Center value (used with tolerance).
|
|
111
|
+
tolerance: Absolute tolerance (+/- from center).
|
|
112
|
+
tolerance_pct: Percentage tolerance (+/- % of center).
|
|
113
|
+
guardband_pct: Guardband as percentage of limit range.
|
|
114
|
+
name: Specification name.
|
|
115
|
+
unit: Unit of measurement.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
LimitSpec instance.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If center requires tolerance or tolerance_pct, or if no limits specified.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> spec = create_limit_spec(center=1.0, tolerance_pct=5) # 1.0 +/- 5%
|
|
125
|
+
>>> spec = create_limit_spec(upper=1.5, lower=0.5, guardband_pct=10)
|
|
126
|
+
"""
|
|
127
|
+
if center is not None:
|
|
128
|
+
if tolerance is not None:
|
|
129
|
+
upper = center + tolerance
|
|
130
|
+
lower = center - tolerance
|
|
131
|
+
elif tolerance_pct is not None:
|
|
132
|
+
abs_tol = abs(center) * tolerance_pct / 100.0
|
|
133
|
+
upper = center + abs_tol
|
|
134
|
+
lower = center - abs_tol
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError("center requires tolerance or tolerance_pct")
|
|
137
|
+
|
|
138
|
+
if upper is None and lower is None:
|
|
139
|
+
raise ValueError("Must specify limits (upper/lower or center+tolerance)")
|
|
140
|
+
|
|
141
|
+
# Calculate guardbands
|
|
142
|
+
upper_gb = 0.0
|
|
143
|
+
lower_gb = 0.0
|
|
144
|
+
if guardband_pct > 0 and upper is not None and lower is not None:
|
|
145
|
+
range_val = upper - lower
|
|
146
|
+
guardband = range_val * guardband_pct / 100.0
|
|
147
|
+
upper_gb = guardband
|
|
148
|
+
lower_gb = guardband
|
|
149
|
+
|
|
150
|
+
return LimitSpec(
|
|
151
|
+
upper=upper,
|
|
152
|
+
lower=lower,
|
|
153
|
+
upper_guardband=upper_gb,
|
|
154
|
+
lower_guardband=lower_gb,
|
|
155
|
+
name=name,
|
|
156
|
+
unit=unit,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def check_limits(
|
|
161
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
162
|
+
limits: LimitSpec | None = None,
|
|
163
|
+
*,
|
|
164
|
+
upper: float | None = None,
|
|
165
|
+
lower: float | None = None,
|
|
166
|
+
reference: float | None = None,
|
|
167
|
+
) -> LimitTestResult:
|
|
168
|
+
"""Check if trace data is within specification limits.
|
|
169
|
+
|
|
170
|
+
Tests all samples against upper and lower limits and returns
|
|
171
|
+
detailed violation information.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
trace: Input trace or data array.
|
|
175
|
+
limits: LimitSpec defining the limits.
|
|
176
|
+
upper: Upper limit (alternative to LimitSpec).
|
|
177
|
+
lower: Lower limit (alternative to LimitSpec).
|
|
178
|
+
reference: Reference value for relative limits.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
LimitTestResult with pass/fail status and violation details.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ValueError: If no limits or bounds specified.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
>>> result = check_limits(trace, upper=1.5, lower=-0.5)
|
|
188
|
+
>>> if not result.passed:
|
|
189
|
+
... print(f"{result.num_violations} violations found")
|
|
190
|
+
"""
|
|
191
|
+
# Get data
|
|
192
|
+
if isinstance(trace, WaveformTrace):
|
|
193
|
+
data = trace.data.astype(np.float64)
|
|
194
|
+
else:
|
|
195
|
+
data = np.asarray(trace, dtype=np.float64)
|
|
196
|
+
|
|
197
|
+
# Create or use limits
|
|
198
|
+
if limits is None:
|
|
199
|
+
if upper is None and lower is None:
|
|
200
|
+
raise ValueError("Must specify limits or upper/lower bounds")
|
|
201
|
+
limits = LimitSpec(upper=upper, lower=lower)
|
|
202
|
+
|
|
203
|
+
# Handle relative limits
|
|
204
|
+
actual_upper = limits.upper
|
|
205
|
+
actual_lower = limits.lower
|
|
206
|
+
if limits.mode == "relative" and reference is not None:
|
|
207
|
+
if actual_upper is not None:
|
|
208
|
+
actual_upper = reference + actual_upper
|
|
209
|
+
if actual_lower is not None:
|
|
210
|
+
actual_lower = reference + actual_lower
|
|
211
|
+
|
|
212
|
+
# Find violations
|
|
213
|
+
upper_viol = np.array([], dtype=np.int64)
|
|
214
|
+
lower_viol = np.array([], dtype=np.int64)
|
|
215
|
+
|
|
216
|
+
if actual_upper is not None:
|
|
217
|
+
upper_viol = np.where(data > actual_upper)[0]
|
|
218
|
+
if actual_lower is not None:
|
|
219
|
+
lower_viol = np.where(data < actual_lower)[0]
|
|
220
|
+
|
|
221
|
+
# Combine violations
|
|
222
|
+
all_violations = np.union1d(upper_viol, lower_viol)
|
|
223
|
+
num_violations = len(all_violations)
|
|
224
|
+
violation_rate = num_violations / len(data) if len(data) > 0 else 0.0
|
|
225
|
+
|
|
226
|
+
# Compute statistics
|
|
227
|
+
max_val = float(np.max(data))
|
|
228
|
+
min_val = float(np.min(data))
|
|
229
|
+
|
|
230
|
+
# Compute margins
|
|
231
|
+
upper_margin = None
|
|
232
|
+
lower_margin = None
|
|
233
|
+
if actual_upper is not None:
|
|
234
|
+
upper_margin = float(actual_upper - max_val)
|
|
235
|
+
if actual_lower is not None:
|
|
236
|
+
lower_margin = float(min_val - actual_lower)
|
|
237
|
+
|
|
238
|
+
# Compute margin percentage
|
|
239
|
+
margin_pct = None
|
|
240
|
+
if actual_upper is not None and actual_lower is not None:
|
|
241
|
+
limit_range = actual_upper - actual_lower
|
|
242
|
+
if limit_range > 0:
|
|
243
|
+
min_margin = min(
|
|
244
|
+
upper_margin if upper_margin is not None else float("inf"),
|
|
245
|
+
lower_margin if lower_margin is not None else float("inf"),
|
|
246
|
+
)
|
|
247
|
+
margin_pct = (min_margin / limit_range) * 100.0
|
|
248
|
+
|
|
249
|
+
# Check guardband
|
|
250
|
+
within_guardband = False
|
|
251
|
+
if num_violations == 0:
|
|
252
|
+
# Check if within guardband
|
|
253
|
+
if limits.upper_guardband > 0 and upper_margin is not None:
|
|
254
|
+
if upper_margin < limits.upper_guardband:
|
|
255
|
+
within_guardband = True
|
|
256
|
+
if limits.lower_guardband > 0 and lower_margin is not None:
|
|
257
|
+
if lower_margin < limits.lower_guardband:
|
|
258
|
+
within_guardband = True
|
|
259
|
+
|
|
260
|
+
return LimitTestResult(
|
|
261
|
+
passed=num_violations == 0,
|
|
262
|
+
num_violations=num_violations,
|
|
263
|
+
violation_rate=violation_rate,
|
|
264
|
+
upper_violations=upper_viol if len(upper_viol) > 0 else None,
|
|
265
|
+
lower_violations=lower_viol if len(lower_viol) > 0 else None,
|
|
266
|
+
max_value=max_val,
|
|
267
|
+
min_value=min_val,
|
|
268
|
+
upper_margin=upper_margin,
|
|
269
|
+
lower_margin=lower_margin,
|
|
270
|
+
margin_percentage=margin_pct,
|
|
271
|
+
within_guardband=within_guardband,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass
|
|
276
|
+
class MarginAnalysis:
|
|
277
|
+
"""Margin analysis result.
|
|
278
|
+
|
|
279
|
+
Attributes:
|
|
280
|
+
upper_margin: Margin to upper limit.
|
|
281
|
+
lower_margin: Margin to lower limit.
|
|
282
|
+
min_margin: Smallest margin (most critical).
|
|
283
|
+
margin_percentage: Margin as percentage of limit range.
|
|
284
|
+
critical_limit: Which limit has the smallest margin.
|
|
285
|
+
warning: True if margin is below warning threshold.
|
|
286
|
+
margin_status: "pass", "warning", or "fail".
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
upper_margin: float | None
|
|
290
|
+
lower_margin: float | None
|
|
291
|
+
min_margin: float
|
|
292
|
+
margin_percentage: float
|
|
293
|
+
critical_limit: Literal["upper", "lower", "both", "none"]
|
|
294
|
+
warning: bool
|
|
295
|
+
margin_status: Literal["pass", "warning", "fail"]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def margin_analysis(
|
|
299
|
+
trace: WaveformTrace | NDArray[np.floating[Any]],
|
|
300
|
+
limits: LimitSpec,
|
|
301
|
+
*,
|
|
302
|
+
warning_threshold_pct: float = 20.0,
|
|
303
|
+
) -> MarginAnalysis:
|
|
304
|
+
"""Analyze margins to specification limits.
|
|
305
|
+
|
|
306
|
+
Calculates how much margin exists between the data and the
|
|
307
|
+
specification limits.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
trace: Input trace or data array.
|
|
311
|
+
limits: LimitSpec defining the limits.
|
|
312
|
+
warning_threshold_pct: Threshold for margin warning (percent).
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
MarginAnalysis with margin details.
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
AnalysisError: If no limits defined for margin analysis.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
>>> margins = margin_analysis(trace, limits)
|
|
322
|
+
>>> print(f"Margin: {margins.margin_percentage:.1f}%")
|
|
323
|
+
"""
|
|
324
|
+
# Get data
|
|
325
|
+
if isinstance(trace, WaveformTrace):
|
|
326
|
+
data = trace.data.astype(np.float64)
|
|
327
|
+
else:
|
|
328
|
+
data = np.asarray(trace, dtype=np.float64)
|
|
329
|
+
|
|
330
|
+
max_val = float(np.max(data))
|
|
331
|
+
min_val = float(np.min(data))
|
|
332
|
+
|
|
333
|
+
# Compute margins
|
|
334
|
+
upper_margin = None
|
|
335
|
+
lower_margin = None
|
|
336
|
+
|
|
337
|
+
if limits.upper is not None:
|
|
338
|
+
upper_margin = limits.upper - max_val
|
|
339
|
+
if limits.lower is not None:
|
|
340
|
+
lower_margin = min_val - limits.lower
|
|
341
|
+
|
|
342
|
+
# Determine minimum margin and critical limit
|
|
343
|
+
margins = []
|
|
344
|
+
if upper_margin is not None:
|
|
345
|
+
margins.append(("upper", upper_margin))
|
|
346
|
+
if lower_margin is not None:
|
|
347
|
+
margins.append(("lower", lower_margin))
|
|
348
|
+
|
|
349
|
+
if not margins:
|
|
350
|
+
raise AnalysisError("No limits defined for margin analysis")
|
|
351
|
+
|
|
352
|
+
# Find minimum margin
|
|
353
|
+
min_margin_tuple = min(margins, key=lambda x: x[1])
|
|
354
|
+
min_margin = min_margin_tuple[1]
|
|
355
|
+
|
|
356
|
+
# Determine critical limit
|
|
357
|
+
if len(margins) == 2 and abs(margins[0][1] - margins[1][1]) < 1e-10:
|
|
358
|
+
critical_limit: Literal["upper", "lower", "both", "none"] = "both"
|
|
359
|
+
else:
|
|
360
|
+
critical_limit = min_margin_tuple[0] # type: ignore[assignment]
|
|
361
|
+
|
|
362
|
+
# Compute margin percentage
|
|
363
|
+
margin_pct = 0.0
|
|
364
|
+
if limits.upper is not None and limits.lower is not None:
|
|
365
|
+
limit_range = limits.upper - limits.lower
|
|
366
|
+
if limit_range > 0:
|
|
367
|
+
margin_pct = (min_margin / limit_range) * 100.0
|
|
368
|
+
elif limits.upper is not None and upper_margin is not None:
|
|
369
|
+
margin_pct = (upper_margin / abs(limits.upper)) * 100.0 if limits.upper != 0 else 0
|
|
370
|
+
elif limits.lower is not None and lower_margin is not None:
|
|
371
|
+
margin_pct = (lower_margin / abs(limits.lower)) * 100.0 if limits.lower != 0 else 0
|
|
372
|
+
|
|
373
|
+
# Determine status
|
|
374
|
+
warning = False
|
|
375
|
+
if min_margin < 0:
|
|
376
|
+
margin_status: Literal["pass", "warning", "fail"] = "fail"
|
|
377
|
+
elif margin_pct < warning_threshold_pct:
|
|
378
|
+
margin_status = "warning"
|
|
379
|
+
warning = True
|
|
380
|
+
else:
|
|
381
|
+
margin_status = "pass"
|
|
382
|
+
|
|
383
|
+
return MarginAnalysis(
|
|
384
|
+
upper_margin=upper_margin,
|
|
385
|
+
lower_margin=lower_margin,
|
|
386
|
+
min_margin=min_margin,
|
|
387
|
+
margin_percentage=margin_pct,
|
|
388
|
+
critical_limit=critical_limit,
|
|
389
|
+
warning=warning,
|
|
390
|
+
margin_status=margin_status,
|
|
391
|
+
)
|