oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Memory warning threshold system for Oscura.
|
|
2
|
+
|
|
3
|
+
This module provides configurable memory pressure warnings with
|
|
4
|
+
automatic alerts and optional operation cancellation.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.memory_warnings import check_memory_warnings, MemoryWarningLevel
|
|
9
|
+
>>> level = check_memory_warnings()
|
|
10
|
+
>>> if level == MemoryWarningLevel.CRITICAL:
|
|
11
|
+
... print("Critical memory pressure!")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
See oscura.config.memory for threshold configuration.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import warnings
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from oscura.config.memory import get_memory_config
|
|
24
|
+
from oscura.utils.memory import get_available_memory, get_memory_pressure
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MemoryWarningLevel(Enum):
|
|
31
|
+
"""Memory warning levels based on pressure thresholds.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
OK: Memory usage is normal (below warn threshold).
|
|
35
|
+
WARNING: Memory usage is elevated (above warn threshold).
|
|
36
|
+
CRITICAL: Memory usage is critical (above critical threshold).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
OK = "ok"
|
|
40
|
+
WARNING = "warning"
|
|
41
|
+
CRITICAL = "critical"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_memory_warnings() -> MemoryWarningLevel:
|
|
45
|
+
"""Check current memory pressure against configured thresholds.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Current memory warning level.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> level = check_memory_warnings()
|
|
53
|
+
>>> if level != MemoryWarningLevel.OK:
|
|
54
|
+
... print(f"Memory pressure: {level.value}")
|
|
55
|
+
"""
|
|
56
|
+
pressure = get_memory_pressure()
|
|
57
|
+
config = get_memory_config()
|
|
58
|
+
|
|
59
|
+
if pressure >= config.critical_threshold:
|
|
60
|
+
return MemoryWarningLevel.CRITICAL
|
|
61
|
+
elif pressure >= config.warn_threshold:
|
|
62
|
+
return MemoryWarningLevel.WARNING
|
|
63
|
+
else:
|
|
64
|
+
return MemoryWarningLevel.OK
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def emit_memory_warning(force: bool = False) -> None:
|
|
68
|
+
"""Emit warning if memory pressure exceeds thresholds.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
force: If True, always emit warning regardless of level.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> emit_memory_warning() # Emits warning if pressure high
|
|
76
|
+
"""
|
|
77
|
+
level = check_memory_warnings()
|
|
78
|
+
pressure = get_memory_pressure()
|
|
79
|
+
available = get_available_memory()
|
|
80
|
+
|
|
81
|
+
if force or level != MemoryWarningLevel.OK:
|
|
82
|
+
if level == MemoryWarningLevel.CRITICAL:
|
|
83
|
+
warnings.warn(
|
|
84
|
+
f"CRITICAL memory pressure: {pressure * 100:.1f}% utilized. "
|
|
85
|
+
f"Only {available / 1e9:.2f} GB available. "
|
|
86
|
+
"Consider closing applications or canceling operations.",
|
|
87
|
+
ResourceWarning,
|
|
88
|
+
stacklevel=2,
|
|
89
|
+
)
|
|
90
|
+
elif level == MemoryWarningLevel.WARNING:
|
|
91
|
+
warnings.warn(
|
|
92
|
+
f"High memory pressure: {pressure * 100:.1f}% utilized. "
|
|
93
|
+
f"{available / 1e9:.2f} GB available. "
|
|
94
|
+
"Monitor memory usage closely.",
|
|
95
|
+
ResourceWarning,
|
|
96
|
+
stacklevel=2,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def check_and_abort_if_critical(operation: str = "operation") -> None:
|
|
101
|
+
"""Check memory and abort if critical threshold exceeded.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
operation: Name of operation to abort.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
MemoryError: If memory pressure is critical.
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> try:
|
|
112
|
+
... check_and_abort_if_critical('spectrogram')
|
|
113
|
+
... # ... perform operation ...
|
|
114
|
+
... except MemoryError:
|
|
115
|
+
... print("Operation aborted due to critical memory")
|
|
116
|
+
"""
|
|
117
|
+
level = check_memory_warnings()
|
|
118
|
+
|
|
119
|
+
if level == MemoryWarningLevel.CRITICAL:
|
|
120
|
+
pressure = get_memory_pressure()
|
|
121
|
+
available = get_available_memory()
|
|
122
|
+
config = get_memory_config()
|
|
123
|
+
|
|
124
|
+
raise MemoryError(
|
|
125
|
+
f"Critical memory pressure ({pressure * 100:.1f}% > {config.critical_threshold * 100:.0f}%). "
|
|
126
|
+
f"Only {available / 1e9:.2f} GB available. "
|
|
127
|
+
f"Operation '{operation}' aborted to prevent system crash. "
|
|
128
|
+
"Free memory or increase critical threshold to proceed."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class MemoryWarningMonitor:
|
|
133
|
+
"""Context manager for monitoring memory warnings during operations.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
Monitors memory pressure during an operation and emits warnings
|
|
137
|
+
when thresholds are crossed.
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
>>> with MemoryWarningMonitor('spectrogram', check_interval=100):
|
|
141
|
+
... for i in range(1000):
|
|
142
|
+
... # ... perform work ...
|
|
143
|
+
... pass
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
operation: str,
|
|
149
|
+
*,
|
|
150
|
+
check_interval: int = 100,
|
|
151
|
+
abort_on_critical: bool = True,
|
|
152
|
+
):
|
|
153
|
+
"""Initialize memory warning monitor.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
operation: Name of operation being monitored.
|
|
157
|
+
check_interval: Check memory every N iterations.
|
|
158
|
+
abort_on_critical: Abort if critical threshold reached.
|
|
159
|
+
"""
|
|
160
|
+
self.operation = operation
|
|
161
|
+
self.check_interval = check_interval
|
|
162
|
+
self.abort_on_critical = abort_on_critical
|
|
163
|
+
self._iteration = 0
|
|
164
|
+
self._warned = False
|
|
165
|
+
self._critical_warned = False
|
|
166
|
+
|
|
167
|
+
def __enter__(self) -> MemoryWarningMonitor:
|
|
168
|
+
"""Enter context and perform initial check."""
|
|
169
|
+
# Initial check
|
|
170
|
+
emit_memory_warning()
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: object) -> None:
|
|
174
|
+
"""Exit context."""
|
|
175
|
+
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
176
|
+
|
|
177
|
+
def check(self, iteration: int | None = None) -> None:
|
|
178
|
+
"""Check memory pressure and emit warnings.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
iteration: Current iteration number (for periodic checking).
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
MemoryError: If critical threshold exceeded and abort_on_critical=True.
|
|
185
|
+
"""
|
|
186
|
+
self._iteration += 1
|
|
187
|
+
|
|
188
|
+
# Only check periodically
|
|
189
|
+
if iteration is not None and iteration % self.check_interval != 0:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
level = check_memory_warnings()
|
|
193
|
+
pressure = get_memory_pressure()
|
|
194
|
+
available = get_available_memory()
|
|
195
|
+
|
|
196
|
+
if level == MemoryWarningLevel.CRITICAL:
|
|
197
|
+
if not self._critical_warned:
|
|
198
|
+
warnings.warn(
|
|
199
|
+
f"Critical memory pressure during {self.operation}: "
|
|
200
|
+
f"{pressure * 100:.1f}% utilized, {available / 1e9:.2f} GB available.",
|
|
201
|
+
ResourceWarning,
|
|
202
|
+
stacklevel=2,
|
|
203
|
+
)
|
|
204
|
+
self._critical_warned = True
|
|
205
|
+
|
|
206
|
+
if self.abort_on_critical:
|
|
207
|
+
raise MemoryError(
|
|
208
|
+
f"Critical memory pressure during {self.operation}. "
|
|
209
|
+
f"Operation aborted to prevent system crash. "
|
|
210
|
+
f"Pressure: {pressure * 100:.1f}%, Available: {available / 1e9:.2f} GB"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
elif level == MemoryWarningLevel.WARNING:
|
|
214
|
+
if not self._warned:
|
|
215
|
+
warnings.warn(
|
|
216
|
+
f"High memory pressure during {self.operation}: "
|
|
217
|
+
f"{pressure * 100:.1f}% utilized, {available / 1e9:.2f} GB available.",
|
|
218
|
+
ResourceWarning,
|
|
219
|
+
stacklevel=2,
|
|
220
|
+
)
|
|
221
|
+
self._warned = True
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def register_memory_warning_callback(callback: Callable[[MemoryWarningLevel], None]) -> None:
|
|
225
|
+
"""Register a callback for memory warnings.
|
|
226
|
+
|
|
227
|
+
The callback will be invoked whenever memory warnings are checked
|
|
228
|
+
via emit_memory_warning() or MemoryWarningMonitor.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
callback: Function that accepts MemoryWarningLevel.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
>>> def my_callback(level):
|
|
235
|
+
... if level == MemoryWarningLevel.CRITICAL:
|
|
236
|
+
... print("CRITICAL MEMORY!")
|
|
237
|
+
>>> register_memory_warning_callback(my_callback)
|
|
238
|
+
"""
|
|
239
|
+
_warning_callbacks.append(callback)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def clear_memory_warning_callbacks() -> None:
|
|
243
|
+
"""Clear all registered memory warning callbacks.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> clear_memory_warning_callbacks()
|
|
247
|
+
"""
|
|
248
|
+
_warning_callbacks.clear()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# Global list of warning callbacks
|
|
252
|
+
_warning_callbacks: list[Callable[[MemoryWarningLevel], None]] = []
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _invoke_callbacks(level: MemoryWarningLevel) -> None:
|
|
256
|
+
"""Invoke all registered callbacks with current warning level.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
level: Current memory warning level.
|
|
260
|
+
"""
|
|
261
|
+
for callback in _warning_callbacks:
|
|
262
|
+
try:
|
|
263
|
+
callback(level)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
warnings.warn(
|
|
266
|
+
f"Memory warning callback failed: {e}",
|
|
267
|
+
RuntimeWarning,
|
|
268
|
+
stacklevel=2,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def format_memory_warning(level: MemoryWarningLevel) -> str:
|
|
273
|
+
"""Format memory warning message.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
level: Memory warning level.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Formatted warning message.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
>>> msg = format_memory_warning(MemoryWarningLevel.WARNING)
|
|
283
|
+
>>> print(msg)
|
|
284
|
+
High memory pressure: 75.0% utilized...
|
|
285
|
+
"""
|
|
286
|
+
pressure = get_memory_pressure()
|
|
287
|
+
available = get_available_memory()
|
|
288
|
+
config = get_memory_config()
|
|
289
|
+
|
|
290
|
+
if level == MemoryWarningLevel.CRITICAL:
|
|
291
|
+
return (
|
|
292
|
+
f"CRITICAL memory pressure: {pressure * 100:.1f}% utilized "
|
|
293
|
+
f"(threshold: {config.critical_threshold * 100:.0f}%). "
|
|
294
|
+
f"Only {available / 1e9:.2f} GB available."
|
|
295
|
+
)
|
|
296
|
+
elif level == MemoryWarningLevel.WARNING:
|
|
297
|
+
return (
|
|
298
|
+
f"High memory pressure: {pressure * 100:.1f}% utilized "
|
|
299
|
+
f"(threshold: {config.warn_threshold * 100:.0f}%). "
|
|
300
|
+
f"{available / 1e9:.2f} GB available."
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
return f"Memory OK: {pressure * 100:.1f}% utilized, {available / 1e9:.2f} GB available."
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
__all__ = [
|
|
307
|
+
"MemoryWarningLevel",
|
|
308
|
+
"MemoryWarningMonitor",
|
|
309
|
+
"check_and_abort_if_critical",
|
|
310
|
+
"check_memory_warnings",
|
|
311
|
+
"clear_memory_warning_callbacks",
|
|
312
|
+
"emit_memory_warning",
|
|
313
|
+
"format_memory_warning",
|
|
314
|
+
"register_memory_warning_callback",
|
|
315
|
+
]
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""Numba JIT compilation backend for performance-critical code paths.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for Numba JIT compilation with graceful
|
|
4
|
+
fallback when Numba is not available. Provides 10-50x speedup for numerical loops
|
|
5
|
+
that cannot be fully vectorized.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from oscura.core.numba_backend import njit, prange, HAS_NUMBA
|
|
9
|
+
|
|
10
|
+
@njit(parallel=True, cache=True)
|
|
11
|
+
def fast_function(data):
|
|
12
|
+
result = np.zeros_like(data)
|
|
13
|
+
for i in prange(len(data)):
|
|
14
|
+
result[i] = expensive_computation(data[i])
|
|
15
|
+
return result
|
|
16
|
+
|
|
17
|
+
Performance characteristics:
|
|
18
|
+
- First call: Compilation overhead (~100-500ms)
|
|
19
|
+
- Subsequent calls: 10-50x faster than Python loops
|
|
20
|
+
- Parallel execution: Additional speedup on multi-core systems
|
|
21
|
+
- Cache: Compilation results cached between runs
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> from oscura.core.numba_backend import njit, HAS_NUMBA
|
|
25
|
+
>>> import numpy as np
|
|
26
|
+
>>>
|
|
27
|
+
>>> @njit(cache=True)
|
|
28
|
+
>>> def sum_of_squares(arr):
|
|
29
|
+
... total = 0.0
|
|
30
|
+
... for i in range(len(arr)):
|
|
31
|
+
... total += arr[i] ** 2
|
|
32
|
+
... return total
|
|
33
|
+
>>>
|
|
34
|
+
>>> data = np.random.randn(1_000_000)
|
|
35
|
+
>>> result = sum_of_squares(data) # Fast on second call
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import functools
|
|
41
|
+
from collections.abc import Callable
|
|
42
|
+
from typing import Any, TypeVar
|
|
43
|
+
|
|
44
|
+
import numpy as np
|
|
45
|
+
|
|
46
|
+
# Try to import Numba
|
|
47
|
+
try:
|
|
48
|
+
from numba import guvectorize as _numba_guvectorize # type: ignore[import-untyped]
|
|
49
|
+
from numba import jit as _numba_jit # type: ignore[import-untyped]
|
|
50
|
+
from numba import njit as _numba_njit # type: ignore[import-untyped]
|
|
51
|
+
from numba import prange as _numba_prange # type: ignore[import-untyped]
|
|
52
|
+
from numba import vectorize as _numba_vectorize # type: ignore[import-untyped]
|
|
53
|
+
|
|
54
|
+
HAS_NUMBA = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
HAS_NUMBA = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Type variable for generic functions
|
|
60
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if HAS_NUMBA:
|
|
64
|
+
# Numba is available - use real implementations
|
|
65
|
+
njit = _numba_njit
|
|
66
|
+
prange = _numba_prange
|
|
67
|
+
vectorize = _numba_vectorize
|
|
68
|
+
guvectorize = _numba_guvectorize
|
|
69
|
+
jit = _numba_jit
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
# Numba not available - provide fallback decorators that do nothing
|
|
73
|
+
def njit(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
74
|
+
"""No-op decorator when Numba is not available.
|
|
75
|
+
|
|
76
|
+
This decorator does nothing but allows code to remain syntactically valid
|
|
77
|
+
when Numba is not installed.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
*args: Positional arguments (ignored).
|
|
81
|
+
**kwargs: Keyword arguments (ignored).
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Decorator function or decorated function.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def decorator(func: F) -> F:
|
|
88
|
+
@functools.wraps(func)
|
|
89
|
+
def wrapper(*call_args: Any, **call_kwargs: Any) -> Any:
|
|
90
|
+
return func(*call_args, **call_kwargs)
|
|
91
|
+
|
|
92
|
+
return wrapper # type: ignore[return-value]
|
|
93
|
+
|
|
94
|
+
# Handle both @njit and @njit() syntax
|
|
95
|
+
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
96
|
+
return decorator(args[0]) # type: ignore[no-any-return]
|
|
97
|
+
return decorator # type: ignore[no-any-return]
|
|
98
|
+
|
|
99
|
+
def prange(*args: Any, **kwargs: Any) -> range:
|
|
100
|
+
"""Fallback to regular range when Numba is not available.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
*args: Same as range().
|
|
104
|
+
**kwargs: Same as range().
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Standard Python range object.
|
|
108
|
+
"""
|
|
109
|
+
return range(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
def vectorize(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
112
|
+
"""No-op decorator when Numba is not available.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
*args: Positional arguments (ignored).
|
|
116
|
+
**kwargs: Keyword arguments (ignored).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Decorator that returns the original function.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def decorator(func: F) -> F:
|
|
123
|
+
return func
|
|
124
|
+
|
|
125
|
+
if len(args) == 1 and callable(args[0]):
|
|
126
|
+
return decorator(args[0]) # type: ignore[no-any-return]
|
|
127
|
+
return decorator # type: ignore[no-any-return]
|
|
128
|
+
|
|
129
|
+
def guvectorize(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
130
|
+
"""No-op decorator when Numba is not available.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
*args: Positional arguments (ignored).
|
|
134
|
+
**kwargs: Keyword arguments (ignored).
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Decorator that returns the original function.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def decorator(func: F) -> F:
|
|
141
|
+
return func
|
|
142
|
+
|
|
143
|
+
if len(args) == 1 and callable(args[0]):
|
|
144
|
+
return decorator(args[0]) # type: ignore[no-any-return]
|
|
145
|
+
return decorator # type: ignore[no-any-return]
|
|
146
|
+
|
|
147
|
+
def jit(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
148
|
+
"""No-op decorator when Numba is not available.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
*args: Positional arguments (ignored).
|
|
152
|
+
**kwargs: Keyword arguments (ignored).
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Decorator that returns the original function.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def decorator(func: F) -> F:
|
|
159
|
+
@functools.wraps(func)
|
|
160
|
+
def wrapper(*call_args: Any, **call_kwargs: Any) -> Any:
|
|
161
|
+
return func(*call_args, **call_kwargs)
|
|
162
|
+
|
|
163
|
+
return wrapper # type: ignore[return-value]
|
|
164
|
+
|
|
165
|
+
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
166
|
+
return decorator(args[0]) # type: ignore[no-any-return]
|
|
167
|
+
return decorator # type: ignore[no-any-return]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_optimal_numba_config(
|
|
171
|
+
parallel: bool = False,
|
|
172
|
+
cache: bool = True,
|
|
173
|
+
fastmath: bool = False,
|
|
174
|
+
nogil: bool = False,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
"""Get optimal Numba configuration for given requirements.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
parallel: Enable parallel execution using prange.
|
|
180
|
+
cache: Enable compilation caching for faster subsequent runs.
|
|
181
|
+
fastmath: Enable fast math optimizations (may reduce precision).
|
|
182
|
+
nogil: Release GIL during execution (useful for threading).
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Dictionary of Numba configuration options.
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> config = get_optimal_numba_config(parallel=True, cache=True)
|
|
189
|
+
>>> @njit(**config)
|
|
190
|
+
>>> def my_function(data):
|
|
191
|
+
... pass
|
|
192
|
+
"""
|
|
193
|
+
if not HAS_NUMBA:
|
|
194
|
+
return {}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"parallel": parallel,
|
|
198
|
+
"cache": cache,
|
|
199
|
+
"fastmath": fastmath,
|
|
200
|
+
"nogil": nogil,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Example Numba-optimized functions for common operations
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@njit(cache=True) # type: ignore[misc,untyped-decorator]
|
|
208
|
+
def find_crossings_numba(
|
|
209
|
+
data: np.ndarray, # type: ignore[type-arg]
|
|
210
|
+
threshold: float,
|
|
211
|
+
direction: int = 0,
|
|
212
|
+
) -> np.ndarray: # type: ignore[type-arg]
|
|
213
|
+
"""Find threshold crossings with Numba acceleration.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
data: Input signal data.
|
|
217
|
+
threshold: Threshold value to detect crossings.
|
|
218
|
+
direction: 0=both, 1=rising only, -1=falling only.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Array of indices where crossings occur.
|
|
222
|
+
"""
|
|
223
|
+
crossings = []
|
|
224
|
+
for i in range(1, len(data)):
|
|
225
|
+
prev_val = data[i - 1]
|
|
226
|
+
curr_val = data[i]
|
|
227
|
+
|
|
228
|
+
if direction >= 0: # Rising or both
|
|
229
|
+
if prev_val < threshold <= curr_val:
|
|
230
|
+
crossings.append(i)
|
|
231
|
+
if direction <= 0 and direction != 1: # Falling or both
|
|
232
|
+
if prev_val > threshold >= curr_val:
|
|
233
|
+
crossings.append(i)
|
|
234
|
+
|
|
235
|
+
return np.array(crossings, dtype=np.int64)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@njit(parallel=True, cache=True) # type: ignore[misc,untyped-decorator]
|
|
239
|
+
def moving_average_numba(
|
|
240
|
+
data: np.ndarray, # type: ignore[type-arg]
|
|
241
|
+
window_size: int,
|
|
242
|
+
) -> np.ndarray: # type: ignore[type-arg]
|
|
243
|
+
"""Compute moving average with Numba parallel acceleration.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
data: Input signal data.
|
|
247
|
+
window_size: Size of the moving window.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Array of moving averages.
|
|
251
|
+
"""
|
|
252
|
+
n = len(data)
|
|
253
|
+
result = np.zeros(n - window_size + 1, dtype=np.float64)
|
|
254
|
+
|
|
255
|
+
for i in prange(len(result)):
|
|
256
|
+
total = 0.0
|
|
257
|
+
for j in range(window_size):
|
|
258
|
+
total += data[i + j]
|
|
259
|
+
result[i] = total / window_size
|
|
260
|
+
|
|
261
|
+
return result
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@njit(cache=True) # type: ignore[misc,untyped-decorator]
|
|
265
|
+
def argrelextrema_numba(
|
|
266
|
+
data: np.ndarray, # type: ignore[type-arg]
|
|
267
|
+
comparator: int,
|
|
268
|
+
order: int = 1,
|
|
269
|
+
) -> np.ndarray: # type: ignore[type-arg]
|
|
270
|
+
"""Find relative extrema (peaks/valleys) with Numba acceleration.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
data: Input signal data.
|
|
274
|
+
comparator: 1 for maxima (peaks), -1 for minima (valleys).
|
|
275
|
+
order: How many points on each side to use for comparison.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Array of indices where extrema occur.
|
|
279
|
+
"""
|
|
280
|
+
extrema = []
|
|
281
|
+
n = len(data)
|
|
282
|
+
|
|
283
|
+
for i in range(order, n - order):
|
|
284
|
+
is_extremum = True
|
|
285
|
+
|
|
286
|
+
for j in range(1, order + 1):
|
|
287
|
+
if comparator > 0: # Maximum
|
|
288
|
+
if data[i] <= data[i - j] or data[i] <= data[i + j]:
|
|
289
|
+
is_extremum = False
|
|
290
|
+
break
|
|
291
|
+
else: # Minimum
|
|
292
|
+
if data[i] >= data[i - j] or data[i] >= data[i + j]:
|
|
293
|
+
is_extremum = False
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
if is_extremum:
|
|
297
|
+
extrema.append(i)
|
|
298
|
+
|
|
299
|
+
return np.array(extrema, dtype=np.int64)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@njit(cache=True) # type: ignore[misc,untyped-decorator]
|
|
303
|
+
def interpolate_linear_numba(
|
|
304
|
+
x: np.ndarray, # type: ignore[type-arg]
|
|
305
|
+
y: np.ndarray, # type: ignore[type-arg]
|
|
306
|
+
x_new: np.ndarray, # type: ignore[type-arg]
|
|
307
|
+
) -> np.ndarray: # type: ignore[type-arg]
|
|
308
|
+
"""Linear interpolation with Numba acceleration.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
x: Original x coordinates (must be sorted).
|
|
312
|
+
y: Original y values.
|
|
313
|
+
x_new: New x coordinates to interpolate.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Interpolated y values at x_new.
|
|
317
|
+
"""
|
|
318
|
+
n = len(x)
|
|
319
|
+
m = len(x_new)
|
|
320
|
+
y_new = np.zeros(m, dtype=np.float64)
|
|
321
|
+
|
|
322
|
+
for i in range(m):
|
|
323
|
+
xi = x_new[i]
|
|
324
|
+
|
|
325
|
+
# Binary search for bracketing indices
|
|
326
|
+
left = 0
|
|
327
|
+
right = n - 1
|
|
328
|
+
|
|
329
|
+
while left < right - 1:
|
|
330
|
+
mid = (left + right) // 2
|
|
331
|
+
if x[mid] <= xi:
|
|
332
|
+
left = mid
|
|
333
|
+
else:
|
|
334
|
+
right = mid
|
|
335
|
+
|
|
336
|
+
# Linear interpolation
|
|
337
|
+
if xi <= x[0]:
|
|
338
|
+
y_new[i] = y[0]
|
|
339
|
+
elif xi >= x[n - 1]:
|
|
340
|
+
y_new[i] = y[n - 1]
|
|
341
|
+
else:
|
|
342
|
+
x0, x1 = x[left], x[right]
|
|
343
|
+
y0, y1 = y[left], y[right]
|
|
344
|
+
t = (xi - x0) / (x1 - x0)
|
|
345
|
+
y_new[i] = y0 + t * (y1 - y0)
|
|
346
|
+
|
|
347
|
+
return y_new
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
__all__ = [
|
|
351
|
+
"HAS_NUMBA",
|
|
352
|
+
"argrelextrema_numba",
|
|
353
|
+
"find_crossings_numba",
|
|
354
|
+
"get_optimal_numba_config",
|
|
355
|
+
"guvectorize",
|
|
356
|
+
"interpolate_linear_numba",
|
|
357
|
+
"jit",
|
|
358
|
+
"moving_average_numba",
|
|
359
|
+
"njit",
|
|
360
|
+
"prange",
|
|
361
|
+
"vectorize",
|
|
362
|
+
]
|