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,523 @@
|
|
|
1
|
+
"""GPU acceleration backend with automatic numpy fallback.
|
|
2
|
+
|
|
3
|
+
This module provides optional GPU acceleration using CuPy with seamless
|
|
4
|
+
fallback to NumPy when CuPy is unavailable or GPU processing is disabled.
|
|
5
|
+
|
|
6
|
+
The GPU backend is lazy-initialized and memory-safe, automatically transferring
|
|
7
|
+
data to/from GPU as needed. GPU usage can be controlled via the environment
|
|
8
|
+
variable OSCURA_USE_GPU (0 to disable, 1 to enable).
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from oscura.core.gpu_backend import gpu
|
|
13
|
+
>>> # Automatically uses GPU if available, numpy otherwise
|
|
14
|
+
>>> freqs = gpu.fft(signal_data)
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Force CPU-only operation
|
|
17
|
+
>>> from oscura.core.gpu_backend import GPUBackend
|
|
18
|
+
>>> cpu_only = GPUBackend(force_cpu=True)
|
|
19
|
+
>>> freqs = cpu_only.fft(signal_data)
|
|
20
|
+
|
|
21
|
+
Configuration:
|
|
22
|
+
Set OSCURA_USE_GPU environment variable to control GPU usage:
|
|
23
|
+
- OSCURA_USE_GPU=0: Force CPU-only operation
|
|
24
|
+
- OSCURA_USE_GPU=1: Enable GPU if available (default)
|
|
25
|
+
|
|
26
|
+
References:
|
|
27
|
+
- CuPy documentation: https://docs.cupy.dev/
|
|
28
|
+
- NumPy FFT module: https://numpy.org/doc/stable/reference/routines.fft.html
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import warnings
|
|
35
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from numpy.typing import NDArray
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GPUBackend:
|
|
44
|
+
"""Optional GPU acceleration with transparent numpy fallback.
|
|
45
|
+
|
|
46
|
+
This class provides GPU-accelerated versions of common array operations
|
|
47
|
+
with automatic fallback to NumPy when CuPy is unavailable or GPU is disabled.
|
|
48
|
+
|
|
49
|
+
GPU availability is checked lazily on first use, and data is automatically
|
|
50
|
+
transferred between CPU and GPU as needed for transparent operation.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
force_cpu: If True, always use CPU (NumPy) even if GPU is available.
|
|
54
|
+
Useful for testing or when GPU memory is limited.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
gpu_available: True if CuPy is available and GPU is enabled.
|
|
58
|
+
using_gpu: True if currently using GPU backend (may differ from
|
|
59
|
+
gpu_available if lazy initialization hasn't occurred yet).
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> backend = GPUBackend()
|
|
63
|
+
>>> if backend.gpu_available:
|
|
64
|
+
... print("Using GPU acceleration")
|
|
65
|
+
>>> else:
|
|
66
|
+
... print("Using CPU (NumPy) fallback")
|
|
67
|
+
>>>
|
|
68
|
+
>>> # All operations work identically regardless of backend
|
|
69
|
+
>>> result = backend.fft(data)
|
|
70
|
+
|
|
71
|
+
References:
|
|
72
|
+
PERF-001 through PERF-004: GPU acceleration requirements
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, force_cpu: bool = False) -> None:
|
|
76
|
+
"""Initialize GPU backend with optional CPU-only mode.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
force_cpu: If True, never use GPU even if available.
|
|
80
|
+
"""
|
|
81
|
+
self._force_cpu = force_cpu
|
|
82
|
+
self._gpu_available: bool | None = None
|
|
83
|
+
self._cp: Any = None # CuPy module if available
|
|
84
|
+
self._initialized = False
|
|
85
|
+
|
|
86
|
+
def _check_gpu(self) -> bool:
|
|
87
|
+
"""Check if GPU/CuPy is available and should be used.
|
|
88
|
+
|
|
89
|
+
This is called lazily on first operation to avoid import overhead
|
|
90
|
+
when GPU is not needed.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if GPU should be used, False to fall back to NumPy.
|
|
94
|
+
"""
|
|
95
|
+
if self._initialized:
|
|
96
|
+
return self._gpu_available or False
|
|
97
|
+
|
|
98
|
+
self._initialized = True
|
|
99
|
+
|
|
100
|
+
# Check environment variable override
|
|
101
|
+
use_gpu_env = os.environ.get("OSCURA_USE_GPU", "1")
|
|
102
|
+
if use_gpu_env == "0" or self._force_cpu:
|
|
103
|
+
self._gpu_available = False
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# Try to import CuPy
|
|
107
|
+
try:
|
|
108
|
+
import cupy as cp # type: ignore[import-not-found]
|
|
109
|
+
|
|
110
|
+
# Verify GPU is actually accessible
|
|
111
|
+
try:
|
|
112
|
+
# Try a simple operation to verify GPU works
|
|
113
|
+
_ = cp.array([1.0])
|
|
114
|
+
self._cp = cp
|
|
115
|
+
self._gpu_available = True
|
|
116
|
+
return True
|
|
117
|
+
except Exception as e:
|
|
118
|
+
warnings.warn(
|
|
119
|
+
f"CuPy is installed but GPU is not accessible: {e}. Falling back to NumPy.",
|
|
120
|
+
RuntimeWarning,
|
|
121
|
+
stacklevel=2,
|
|
122
|
+
)
|
|
123
|
+
self._gpu_available = False
|
|
124
|
+
return False
|
|
125
|
+
except ImportError:
|
|
126
|
+
# CuPy not installed - silent fallback
|
|
127
|
+
self._gpu_available = False
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def gpu_available(self) -> bool:
|
|
132
|
+
"""Check if GPU acceleration is available.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if CuPy is available and GPU can be used.
|
|
136
|
+
"""
|
|
137
|
+
if not self._initialized:
|
|
138
|
+
self._check_gpu()
|
|
139
|
+
return self._gpu_available or False
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def using_gpu(self) -> bool:
|
|
143
|
+
"""Alias for gpu_available for backwards compatibility.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if currently using GPU backend.
|
|
147
|
+
"""
|
|
148
|
+
return self.gpu_available
|
|
149
|
+
|
|
150
|
+
def _to_cpu(self, array: Any) -> NDArray[Any]:
|
|
151
|
+
"""Transfer array from GPU to CPU if needed.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
array: Array that may be on GPU or CPU.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
NumPy array on CPU.
|
|
158
|
+
"""
|
|
159
|
+
if self.gpu_available and self._cp is not None:
|
|
160
|
+
if isinstance(array, self._cp.ndarray):
|
|
161
|
+
return self._cp.asnumpy(array) # type: ignore[no-any-return]
|
|
162
|
+
return np.asarray(array)
|
|
163
|
+
|
|
164
|
+
def _to_gpu(self, array: NDArray[Any]) -> Any:
|
|
165
|
+
"""Transfer array from CPU to GPU if GPU is enabled.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
array: NumPy array on CPU.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
CuPy array on GPU if GPU is available, otherwise NumPy array.
|
|
172
|
+
"""
|
|
173
|
+
if self.gpu_available and self._cp is not None:
|
|
174
|
+
return self._cp.asarray(array)
|
|
175
|
+
return array
|
|
176
|
+
|
|
177
|
+
def fft(
|
|
178
|
+
self,
|
|
179
|
+
data: NDArray[np.complex128] | NDArray[np.float64],
|
|
180
|
+
n: int | None = None,
|
|
181
|
+
axis: int = -1,
|
|
182
|
+
norm: Literal["backward", "ortho", "forward"] | None = None,
|
|
183
|
+
) -> NDArray[np.complex128]:
|
|
184
|
+
"""GPU-accelerated FFT with automatic fallback to NumPy.
|
|
185
|
+
|
|
186
|
+
Computes the one-dimensional discrete Fourier Transform using GPU
|
|
187
|
+
if available, otherwise falls back to NumPy.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
data: Input array (can be real or complex).
|
|
191
|
+
n: Length of the transformed axis. If None, uses data.shape[axis].
|
|
192
|
+
axis: Axis over which to compute the FFT.
|
|
193
|
+
norm: Normalization mode ("backward", "ortho", or "forward").
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Complex-valued FFT of the input array (always NumPy array on CPU).
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> signal = np.random.randn(1000)
|
|
200
|
+
>>> spectrum = gpu.fft(signal)
|
|
201
|
+
>>> # Result is always NumPy array, regardless of backend
|
|
202
|
+
|
|
203
|
+
References:
|
|
204
|
+
SPE-001: Standard FFT Computation
|
|
205
|
+
PERF-001: GPU-accelerated FFT
|
|
206
|
+
"""
|
|
207
|
+
if self._check_gpu() and self._cp is not None:
|
|
208
|
+
# GPU path
|
|
209
|
+
gpu_data = self._to_gpu(data)
|
|
210
|
+
result = self._cp.fft.fft(gpu_data, n=n, axis=axis, norm=norm)
|
|
211
|
+
return self._to_cpu(result)
|
|
212
|
+
else:
|
|
213
|
+
# CPU fallback
|
|
214
|
+
return np.fft.fft(data, n=n, axis=axis, norm=norm)
|
|
215
|
+
|
|
216
|
+
def ifft(
|
|
217
|
+
self,
|
|
218
|
+
data: NDArray[np.complex128],
|
|
219
|
+
n: int | None = None,
|
|
220
|
+
axis: int = -1,
|
|
221
|
+
norm: Literal["backward", "ortho", "forward"] | None = None,
|
|
222
|
+
) -> NDArray[np.complex128]:
|
|
223
|
+
"""GPU-accelerated inverse FFT with automatic fallback.
|
|
224
|
+
|
|
225
|
+
Computes the one-dimensional inverse discrete Fourier Transform.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
data: Input complex array.
|
|
229
|
+
n: Length of the transformed axis. If None, uses data.shape[axis].
|
|
230
|
+
axis: Axis over which to compute the IFFT.
|
|
231
|
+
norm: Normalization mode ("backward", "ortho", or "forward").
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Complex-valued IFFT of the input array (always NumPy array on CPU).
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> spectrum = np.fft.fft(signal)
|
|
238
|
+
>>> recovered = gpu.ifft(spectrum)
|
|
239
|
+
|
|
240
|
+
References:
|
|
241
|
+
SPE-001: Standard FFT Computation
|
|
242
|
+
PERF-001: GPU-accelerated FFT
|
|
243
|
+
"""
|
|
244
|
+
if self._check_gpu() and self._cp is not None:
|
|
245
|
+
# GPU path
|
|
246
|
+
gpu_data = self._to_gpu(data)
|
|
247
|
+
result = self._cp.fft.ifft(gpu_data, n=n, axis=axis, norm=norm)
|
|
248
|
+
return self._to_cpu(result)
|
|
249
|
+
else:
|
|
250
|
+
# CPU fallback
|
|
251
|
+
return np.fft.ifft(data, n=n, axis=axis, norm=norm)
|
|
252
|
+
|
|
253
|
+
def rfft(
|
|
254
|
+
self,
|
|
255
|
+
data: NDArray[np.float64],
|
|
256
|
+
n: int | None = None,
|
|
257
|
+
axis: int = -1,
|
|
258
|
+
norm: Literal["backward", "ortho", "forward"] | None = None,
|
|
259
|
+
) -> NDArray[np.complex128]:
|
|
260
|
+
"""GPU-accelerated real FFT with automatic fallback.
|
|
261
|
+
|
|
262
|
+
Computes the one-dimensional FFT of real-valued input, returning
|
|
263
|
+
only the positive frequency components (memory efficient).
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
data: Input real-valued array.
|
|
267
|
+
n: Length of the transformed axis. If None, uses data.shape[axis].
|
|
268
|
+
axis: Axis over which to compute the FFT.
|
|
269
|
+
norm: Normalization mode ("backward", "ortho", or "forward").
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Complex-valued FFT (positive frequencies only) on CPU.
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
>>> signal = np.random.randn(1000)
|
|
276
|
+
>>> spectrum = gpu.rfft(signal)
|
|
277
|
+
>>> # Result has length n//2 + 1
|
|
278
|
+
|
|
279
|
+
References:
|
|
280
|
+
SPE-001: Standard FFT Computation
|
|
281
|
+
PERF-001: GPU-accelerated FFT
|
|
282
|
+
"""
|
|
283
|
+
if self._check_gpu() and self._cp is not None:
|
|
284
|
+
# GPU path
|
|
285
|
+
gpu_data = self._to_gpu(data)
|
|
286
|
+
result = self._cp.fft.rfft(gpu_data, n=n, axis=axis, norm=norm)
|
|
287
|
+
return self._to_cpu(result)
|
|
288
|
+
else:
|
|
289
|
+
# CPU fallback
|
|
290
|
+
return np.fft.rfft(data, n=n, axis=axis, norm=norm)
|
|
291
|
+
|
|
292
|
+
def irfft(
|
|
293
|
+
self,
|
|
294
|
+
data: NDArray[np.complex128],
|
|
295
|
+
n: int | None = None,
|
|
296
|
+
axis: int = -1,
|
|
297
|
+
norm: Literal["backward", "ortho", "forward"] | None = None,
|
|
298
|
+
) -> NDArray[np.float64]:
|
|
299
|
+
"""GPU-accelerated inverse real FFT with automatic fallback.
|
|
300
|
+
|
|
301
|
+
Computes the inverse FFT of rfft, returning real-valued output.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
data: Input complex array (from rfft).
|
|
305
|
+
n: Length of output. If None, uses (data.shape[axis] - 1) * 2.
|
|
306
|
+
axis: Axis over which to compute the IFFT.
|
|
307
|
+
norm: Normalization mode ("backward", "ortho", or "forward").
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Real-valued IFFT on CPU.
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
>>> spectrum = gpu.rfft(signal)
|
|
314
|
+
>>> recovered = gpu.irfft(spectrum)
|
|
315
|
+
|
|
316
|
+
References:
|
|
317
|
+
SPE-001: Standard FFT Computation
|
|
318
|
+
PERF-001: GPU-accelerated FFT
|
|
319
|
+
"""
|
|
320
|
+
if self._check_gpu() and self._cp is not None:
|
|
321
|
+
# GPU path
|
|
322
|
+
gpu_data = self._to_gpu(data)
|
|
323
|
+
result = self._cp.fft.irfft(gpu_data, n=n, axis=axis, norm=norm)
|
|
324
|
+
return self._to_cpu(result)
|
|
325
|
+
else:
|
|
326
|
+
# CPU fallback
|
|
327
|
+
return np.fft.irfft(data, n=n, axis=axis, norm=norm)
|
|
328
|
+
|
|
329
|
+
def convolve(
|
|
330
|
+
self,
|
|
331
|
+
data: NDArray[np.float64],
|
|
332
|
+
kernel: NDArray[np.float64],
|
|
333
|
+
mode: Literal["full", "valid", "same"] = "full",
|
|
334
|
+
) -> NDArray[np.float64]:
|
|
335
|
+
"""GPU-accelerated convolution with automatic fallback.
|
|
336
|
+
|
|
337
|
+
Computes the discrete linear convolution of data with kernel.
|
|
338
|
+
Uses FFT-based convolution for efficiency on large arrays.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
data: Input signal array.
|
|
342
|
+
kernel: Convolution kernel (filter coefficients).
|
|
343
|
+
mode: Convolution mode:
|
|
344
|
+
- "full": Full convolution (length N + M - 1)
|
|
345
|
+
- "valid": Only where data and kernel fully overlap
|
|
346
|
+
- "same": Same length as data (centered)
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Convolved array on CPU.
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
>>> signal = np.random.randn(1000)
|
|
353
|
+
>>> kernel = np.array([0.25, 0.5, 0.25]) # Simple smoothing
|
|
354
|
+
>>> smoothed = gpu.convolve(signal, kernel, mode="same")
|
|
355
|
+
|
|
356
|
+
References:
|
|
357
|
+
PERF-002: GPU-accelerated convolution
|
|
358
|
+
"""
|
|
359
|
+
if self._check_gpu() and self._cp is not None:
|
|
360
|
+
# GPU path
|
|
361
|
+
gpu_data = self._to_gpu(data)
|
|
362
|
+
gpu_kernel = self._to_gpu(kernel)
|
|
363
|
+
result = self._cp.convolve(gpu_data, gpu_kernel, mode=mode)
|
|
364
|
+
return self._to_cpu(result)
|
|
365
|
+
else:
|
|
366
|
+
# CPU fallback
|
|
367
|
+
return np.convolve(data, kernel, mode=mode)
|
|
368
|
+
|
|
369
|
+
def correlate(
|
|
370
|
+
self,
|
|
371
|
+
a: NDArray[np.float64],
|
|
372
|
+
v: NDArray[np.float64],
|
|
373
|
+
mode: Literal["full", "valid", "same"] = "full",
|
|
374
|
+
) -> NDArray[np.float64]:
|
|
375
|
+
"""GPU-accelerated correlation with automatic fallback.
|
|
376
|
+
|
|
377
|
+
Computes the cross-correlation of two 1-dimensional sequences.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
a: First input sequence.
|
|
381
|
+
v: Second input sequence.
|
|
382
|
+
mode: Correlation mode ("full", "valid", or "same").
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Cross-correlation on CPU.
|
|
386
|
+
|
|
387
|
+
Example:
|
|
388
|
+
>>> signal = np.random.randn(1000)
|
|
389
|
+
>>> template = signal[100:200]
|
|
390
|
+
>>> corr = gpu.correlate(signal, template, mode="valid")
|
|
391
|
+
>>> # Find best match location
|
|
392
|
+
>>> match_idx = np.argmax(corr)
|
|
393
|
+
|
|
394
|
+
References:
|
|
395
|
+
PERF-003: GPU-accelerated pattern matching
|
|
396
|
+
"""
|
|
397
|
+
if self._check_gpu() and self._cp is not None:
|
|
398
|
+
# GPU path
|
|
399
|
+
gpu_a = self._to_gpu(a)
|
|
400
|
+
gpu_v = self._to_gpu(v)
|
|
401
|
+
result = self._cp.correlate(gpu_a, gpu_v, mode=mode)
|
|
402
|
+
return self._to_cpu(result)
|
|
403
|
+
else:
|
|
404
|
+
# CPU fallback
|
|
405
|
+
return np.correlate(a, v, mode=mode)
|
|
406
|
+
|
|
407
|
+
def histogram(
|
|
408
|
+
self,
|
|
409
|
+
data: NDArray[np.float64],
|
|
410
|
+
bins: int | NDArray[np.float64] = 10,
|
|
411
|
+
range: tuple[float, float] | None = None,
|
|
412
|
+
density: bool = False,
|
|
413
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
414
|
+
"""GPU-accelerated histogram with automatic fallback.
|
|
415
|
+
|
|
416
|
+
Computes the histogram of a dataset, useful for statistical analysis
|
|
417
|
+
and signal quality metrics.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
data: Input data array.
|
|
421
|
+
bins: Number of bins or array of bin edges.
|
|
422
|
+
range: Lower and upper range of bins. If None, uses (data.min(), data.max()).
|
|
423
|
+
density: If True, return probability density instead of counts.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Tuple of (counts, bin_edges) both on CPU.
|
|
427
|
+
|
|
428
|
+
Example:
|
|
429
|
+
>>> signal = np.random.randn(10000)
|
|
430
|
+
>>> counts, edges = gpu.histogram(signal, bins=100)
|
|
431
|
+
>>> # Plot histogram
|
|
432
|
+
>>> plt.bar(edges[:-1], counts, width=np.diff(edges))
|
|
433
|
+
|
|
434
|
+
References:
|
|
435
|
+
PERF-004: GPU-accelerated histogram computation
|
|
436
|
+
"""
|
|
437
|
+
if self._check_gpu() and self._cp is not None:
|
|
438
|
+
# GPU path
|
|
439
|
+
gpu_data = self._to_gpu(data)
|
|
440
|
+
# Transfer bins to GPU if it's an array (not just int)
|
|
441
|
+
gpu_bins = self._to_gpu(bins) if isinstance(bins, np.ndarray) else bins
|
|
442
|
+
counts, edges = self._cp.histogram(
|
|
443
|
+
gpu_data, bins=gpu_bins, range=range, density=density
|
|
444
|
+
)
|
|
445
|
+
return self._to_cpu(counts), self._to_cpu(edges)
|
|
446
|
+
else:
|
|
447
|
+
# CPU fallback
|
|
448
|
+
return np.histogram(data, bins=bins, range=range, density=density) # type: ignore[no-any-return]
|
|
449
|
+
|
|
450
|
+
def dot(
|
|
451
|
+
self,
|
|
452
|
+
a: NDArray[np.float64],
|
|
453
|
+
b: NDArray[np.float64],
|
|
454
|
+
) -> NDArray[np.float64] | np.float64:
|
|
455
|
+
"""GPU-accelerated dot product with automatic fallback.
|
|
456
|
+
|
|
457
|
+
Computes the dot product of two arrays, useful for correlation
|
|
458
|
+
and pattern matching operations.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
a: First array.
|
|
462
|
+
b: Second array.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Dot product on CPU (scalar or array depending on input dimensions).
|
|
466
|
+
|
|
467
|
+
Example:
|
|
468
|
+
>>> a = np.random.randn(1000)
|
|
469
|
+
>>> b = np.random.randn(1000)
|
|
470
|
+
>>> similarity = gpu.dot(a, b)
|
|
471
|
+
|
|
472
|
+
References:
|
|
473
|
+
PERF-003: GPU-accelerated matrix operations
|
|
474
|
+
"""
|
|
475
|
+
if self._check_gpu() and self._cp is not None:
|
|
476
|
+
# GPU path
|
|
477
|
+
gpu_a = self._to_gpu(a)
|
|
478
|
+
gpu_b = self._to_gpu(b)
|
|
479
|
+
result = self._cp.dot(gpu_a, gpu_b)
|
|
480
|
+
return self._to_cpu(result) # type: ignore[return-value]
|
|
481
|
+
else:
|
|
482
|
+
# CPU fallback
|
|
483
|
+
return np.dot(a, b) # type: ignore[return-value, no-any-return]
|
|
484
|
+
|
|
485
|
+
def matmul(
|
|
486
|
+
self,
|
|
487
|
+
a: NDArray[np.float64],
|
|
488
|
+
b: NDArray[np.float64],
|
|
489
|
+
) -> NDArray[np.float64]:
|
|
490
|
+
"""GPU-accelerated matrix multiplication with automatic fallback.
|
|
491
|
+
|
|
492
|
+
Computes the matrix product of two arrays.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
a: First matrix.
|
|
496
|
+
b: Second matrix.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Matrix product on CPU.
|
|
500
|
+
|
|
501
|
+
Example:
|
|
502
|
+
>>> A = np.random.randn(100, 50)
|
|
503
|
+
>>> B = np.random.randn(50, 100)
|
|
504
|
+
>>> C = gpu.matmul(A, B)
|
|
505
|
+
|
|
506
|
+
References:
|
|
507
|
+
PERF-003: GPU-accelerated matrix operations
|
|
508
|
+
"""
|
|
509
|
+
if self._check_gpu() and self._cp is not None:
|
|
510
|
+
# GPU path
|
|
511
|
+
gpu_a = self._to_gpu(a)
|
|
512
|
+
gpu_b = self._to_gpu(b)
|
|
513
|
+
result = self._cp.matmul(gpu_a, gpu_b)
|
|
514
|
+
return self._to_cpu(result) # type: ignore[return-value]
|
|
515
|
+
else:
|
|
516
|
+
# CPU fallback
|
|
517
|
+
return np.matmul(a, b) # type: ignore[return-value, no-any-return]
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# Module-level singleton for convenient access
|
|
521
|
+
gpu = GPUBackend()
|
|
522
|
+
|
|
523
|
+
__all__ = ["GPUBackend", "gpu"]
|