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.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/core/lazy.py ADDED
@@ -0,0 +1,832 @@
1
+ """Lazy evaluation module for deferred computation in Oscura workflows.
2
+
3
+ This module provides lazy evaluation primitives that defer computation until
4
+ results are actually accessed. Designed for analysis workflows where not all
5
+ intermediate results may be needed, reducing memory usage and computation time.
6
+
7
+ Key features:
8
+ - Thread-safe lazy evaluation with compute-once semantics
9
+ - Chained operations without eager evaluation
10
+ - Partial evaluation (compute subset of results)
11
+ - Memory-efficient release of source data after computation
12
+ - Integration with Oscura's memory monitoring and progress tracking
13
+
14
+
15
+ Example:
16
+ >>> from oscura.core.lazy import LazyResult, lazy, LazyDict
17
+ >>>
18
+ >>> # Defer expensive FFT computation
19
+ >>> @lazy
20
+ >>> def compute_fft(signal, nfft):
21
+ ... return np.fft.fft(signal, n=nfft)
22
+ >>>
23
+ >>> # Create lazy result - not computed yet
24
+ >>> lazy_fft = compute_fft(signal, 8192)
25
+ >>> print(lazy_fft.is_computed()) # False
26
+ >>>
27
+ >>> # Access triggers computation
28
+ >>> spectrum = lazy_fft.value # Computed now
29
+ >>> print(lazy_fft.is_computed()) # True
30
+ >>>
31
+ >>> # Multiple accesses use cached result
32
+ >>> spectrum2 = lazy_fft.value # Returns cached value
33
+ >>>
34
+ >>> # LazyDict for multiple lazy results
35
+ >>> results = LazyDict()
36
+ >>> results['fft'] = LazyResult(lambda: np.fft.fft(signal, 8192))
37
+ >>> results['power'] = LazyResult(lambda: np.abs(results['fft'].value)**2)
38
+ >>> # Access triggers computation chain
39
+ >>> power_spectrum = results['power'] # Computes fft, then power
40
+
41
+ References:
42
+ Python lazy evaluation patterns
43
+ Threading locks for thread-safe computation
44
+ Oscura memory monitoring (core.memory_monitor)
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import functools
50
+ import threading
51
+ from dataclasses import dataclass
52
+ from typing import TYPE_CHECKING, Any
53
+
54
+ if TYPE_CHECKING:
55
+ from collections.abc import Callable
56
+
57
+
58
+ @dataclass
59
+ class LazyComputeStats:
60
+ """Statistics for lazy computation tracking.
61
+
62
+ Attributes:
63
+ total_created: Total number of LazyResult instances created.
64
+ total_computed: Number of LazyResult instances that have been computed.
65
+ total_invalidated: Number of invalidations (for delta analysis).
66
+ compute_time_total: Total time spent computing (seconds).
67
+ cache_hits: Number of times a computed result was reused.
68
+
69
+ Example:
70
+ >>> stats = LazyComputeStats()
71
+ >>> stats.total_created += 1
72
+ >>> stats.total_computed += 1
73
+ >>> print(f"Computed: {stats.total_computed}/{stats.total_created}")
74
+ """
75
+
76
+ total_created: int = 0
77
+ total_computed: int = 0
78
+ total_invalidated: int = 0
79
+ compute_time_total: float = 0.0
80
+ cache_hits: int = 0
81
+
82
+ @property
83
+ def hit_rate(self) -> float:
84
+ """Calculate cache hit rate.
85
+
86
+ Returns:
87
+ Fraction of accesses that were cache hits (0.0-1.0).
88
+ """
89
+ total_accesses = self.total_computed + self.cache_hits
90
+ if total_accesses == 0:
91
+ return 0.0
92
+ return self.cache_hits / total_accesses
93
+
94
+ def __str__(self) -> str:
95
+ """Format statistics as readable string."""
96
+ return (
97
+ f"Lazy Computation Statistics:\n"
98
+ f" Created: {self.total_created}\n"
99
+ f" Computed: {self.total_computed}\n"
100
+ f" Cache Hits: {self.cache_hits}\n"
101
+ f" Hit Rate: {self.hit_rate * 100:.1f}%\n"
102
+ f" Invalidations: {self.total_invalidated}\n"
103
+ f" Total Compute Time: {self.compute_time_total:.3f}s\n"
104
+ )
105
+
106
+
107
+ # Global statistics tracker
108
+ _global_stats = LazyComputeStats()
109
+ _stats_lock = threading.Lock()
110
+
111
+
112
+ def get_lazy_stats() -> LazyComputeStats:
113
+ """Get global lazy computation statistics.
114
+
115
+ Returns:
116
+ Global LazyComputeStats instance.
117
+
118
+ Example:
119
+ >>> stats = get_lazy_stats()
120
+ >>> print(stats)
121
+ Lazy Computation Statistics:
122
+ Created: 42
123
+ Computed: 35
124
+ ...
125
+
126
+ References:
127
+ MEM-031: Cache statistics tracking
128
+ """
129
+ with _stats_lock:
130
+ return LazyComputeStats(
131
+ total_created=_global_stats.total_created,
132
+ total_computed=_global_stats.total_computed,
133
+ total_invalidated=_global_stats.total_invalidated,
134
+ compute_time_total=_global_stats.compute_time_total,
135
+ cache_hits=_global_stats.cache_hits,
136
+ )
137
+
138
+
139
+ def reset_lazy_stats() -> None:
140
+ """Reset global lazy computation statistics.
141
+
142
+ Example:
143
+ >>> reset_lazy_stats()
144
+ >>> stats = get_lazy_stats()
145
+ >>> assert stats.total_created == 0
146
+ """
147
+ global _global_stats
148
+ with _stats_lock:
149
+ _global_stats = LazyComputeStats()
150
+
151
+
152
+ class LazyResult[T]:
153
+ """Deferred computation wrapper with thread-safe compute-once semantics.
154
+
155
+ Wraps a computation function that will be called only when the result is
156
+ first accessed. Subsequent accesses return the cached result. Thread-safe
157
+ for parallel access from multiple analyzers.
158
+
159
+
160
+ Attributes:
161
+ name: Optional name for debugging/logging.
162
+
163
+ Example:
164
+ >>> # Create lazy FFT computation
165
+ >>> lazy_fft = LazyResult(
166
+ ... lambda: np.fft.fft(signal, n=8192),
167
+ ... name="fft_8192"
168
+ ... )
169
+ >>>
170
+ >>> # Check if computed without triggering computation
171
+ >>> if not lazy_fft.is_computed():
172
+ ... print("Not computed yet")
173
+ >>>
174
+ >>> # Access triggers computation
175
+ >>> spectrum = lazy_fft.value
176
+ >>>
177
+ >>> # Subsequent accesses use cache
178
+ >>> spectrum2 = lazy_fft.value # No recomputation
179
+ >>>
180
+ >>> # Invalidate for delta analysis
181
+ >>> lazy_fft.invalidate()
182
+ >>> spectrum3 = lazy_fft.value # Recomputes
183
+
184
+ References:
185
+ Python threading for thread safety
186
+ PERF-002: Lazy evaluation requirements
187
+ """
188
+
189
+ def __init__(
190
+ self,
191
+ compute_fn: Callable[[], T],
192
+ name: str = "",
193
+ *,
194
+ weak_source: bool = False,
195
+ ):
196
+ """Initialize lazy result.
197
+
198
+ Args:
199
+ compute_fn: Function to call to compute the result.
200
+ name: Optional name for debugging/logging.
201
+ weak_source: If True, use weak reference to source data
202
+ (allows GC after computation).
203
+
204
+ Example:
205
+ >>> lazy_result = LazyResult(
206
+ ... lambda: expensive_computation(),
207
+ ... name="expensive_op"
208
+ ... )
209
+ """
210
+ self._compute_fn = compute_fn
211
+ self._name = name or f"LazyResult_{id(self)}"
212
+ self._result: T | None = None
213
+ self._computed = False
214
+ self._lock = threading.RLock()
215
+ self._weak_source = weak_source
216
+ self._source_released = False
217
+
218
+ # Track creation
219
+ with _stats_lock:
220
+ _global_stats.total_created += 1
221
+
222
+ @property
223
+ def value(self) -> T:
224
+ """Get the result, computing if necessary.
225
+
226
+ Thread-safe lazy computation with compute-once semantics.
227
+ Multiple concurrent accesses will only compute once.
228
+
229
+ Returns:
230
+ The computed result.
231
+
232
+ Raises:
233
+ Exception: Any exception raised by the compute function.
234
+
235
+ Example:
236
+ >>> lazy_fft = LazyResult(lambda: np.fft.fft(signal))
237
+ >>> spectrum = lazy_fft.value # Computes here
238
+ >>> spectrum2 = lazy_fft.value # Uses cache
239
+
240
+ References:
241
+ PERF-002: Lazy evaluation for analysis pipelines
242
+ """
243
+ with self._lock:
244
+ if self._computed:
245
+ # Cache hit
246
+ with _stats_lock:
247
+ _global_stats.cache_hits += 1
248
+ return self._result # type: ignore[return-value]
249
+
250
+ # Compute result
251
+ import time
252
+
253
+ start_time = time.time()
254
+
255
+ try:
256
+ self._result = self._compute_fn()
257
+ self._computed = True
258
+
259
+ # Track computation
260
+ compute_time = time.time() - start_time
261
+ with _stats_lock:
262
+ _global_stats.total_computed += 1
263
+ _global_stats.compute_time_total += compute_time
264
+
265
+ # Optionally release source data
266
+ if self._weak_source and not self._source_released:
267
+ self._release_source()
268
+
269
+ return self._result
270
+
271
+ except Exception:
272
+ # Don't cache errors, allow retry
273
+ self._computed = False
274
+ raise
275
+
276
+ def is_computed(self) -> bool:
277
+ """Check if result has been computed without triggering computation.
278
+
279
+ Returns:
280
+ True if result is computed and cached.
281
+
282
+ Example:
283
+ >>> lazy_result = LazyResult(lambda: expensive_op())
284
+ >>> if lazy_result.is_computed():
285
+ ... result = lazy_result.value # No computation
286
+ ... else:
287
+ ... print("Will compute on next access")
288
+
289
+ References:
290
+ API-012: Lazy result access patterns
291
+ """
292
+ with self._lock:
293
+ return self._computed
294
+
295
+ def invalidate(self) -> None:
296
+ """Mark result as invalid, forcing recomputation on next access.
297
+
298
+ Useful for delta analysis where the underlying data changes and
299
+ results need to be recomputed.
300
+
301
+ Example:
302
+ >>> lazy_fft = LazyResult(lambda: np.fft.fft(signal))
303
+ >>> spectrum1 = lazy_fft.value
304
+ >>>
305
+ >>> # Signal data changed
306
+ >>> signal = new_signal
307
+ >>> lazy_fft.invalidate()
308
+ >>>
309
+ >>> # Next access recomputes with new signal
310
+ >>> spectrum2 = lazy_fft.value
311
+
312
+ References:
313
+ API-012: Delta analysis support
314
+ """
315
+ with self._lock:
316
+ self._computed = False
317
+ self._result = None
318
+ self._source_released = False
319
+
320
+ with _stats_lock:
321
+ _global_stats.total_invalidated += 1
322
+
323
+ def get_if_computed(self) -> T | None:
324
+ """Get result only if already computed, otherwise return None.
325
+
326
+ Returns:
327
+ Computed result or None if not yet computed.
328
+
329
+ Example:
330
+ >>> lazy_result = LazyResult(lambda: expensive_op())
331
+ >>> result = lazy_result.get_if_computed() # None
332
+ >>> _ = lazy_result.value # Compute
333
+ >>> result = lazy_result.get_if_computed() # Returns result
334
+ """
335
+ with self._lock:
336
+ if self._computed:
337
+ return self._result
338
+ return None
339
+
340
+ def peek(self) -> tuple[bool, T | None]:
341
+ """Get computation status and result if available.
342
+
343
+ Returns:
344
+ Tuple of (is_computed, result). Result is None if not computed.
345
+
346
+ Example:
347
+ >>> lazy_result = LazyResult(lambda: expensive_op())
348
+ >>> computed, result = lazy_result.peek()
349
+ >>> if computed:
350
+ ... print(f"Result: {result}")
351
+ ... else:
352
+ ... print("Not computed yet")
353
+ """
354
+ with self._lock:
355
+ return (self._computed, self._result)
356
+
357
+ def map(self, fn: Callable[[T], Any]) -> LazyResult[Any]:
358
+ """Create a new lazy result by applying a function to this result.
359
+
360
+ Enables chained lazy operations without eager evaluation.
361
+
362
+ Args:
363
+ fn: Function to apply to the result.
364
+
365
+ Returns:
366
+ New LazyResult that computes fn(self.value).
367
+
368
+ Example:
369
+ >>> lazy_fft = LazyResult(lambda: np.fft.fft(signal))
370
+ >>> lazy_power = lazy_fft.map(lambda x: np.abs(x)**2)
371
+ >>> lazy_peak = lazy_power.map(lambda x: x.max())
372
+ >>>
373
+ >>> # Nothing computed yet
374
+ >>> peak = lazy_peak.value # Computes entire chain
375
+
376
+ References:
377
+ PERF-002: Lazy evaluation for chained operations
378
+ """
379
+ return LazyResult(
380
+ lambda: fn(self.value),
381
+ name=f"{self._name}.map({fn.__name__})",
382
+ weak_source=self._weak_source,
383
+ )
384
+
385
+ def _release_source(self) -> None:
386
+ """Release source data to allow garbage collection.
387
+
388
+ After computation completes, we can release the source data
389
+ if weak_source=True was specified. This replaces the compute
390
+ function's closure to break references to large input data.
391
+
392
+ Note: We don't call gc.collect() here as it would be called
393
+ very frequently and is expensive. Python's automatic GC will
394
+ handle cleanup.
395
+ """
396
+ # Clear the compute function's closure to release references
397
+ if hasattr(self._compute_fn, "__closure__") and self._compute_fn.__closure__:
398
+ # Can't directly clear closure, but can replace function
399
+ # to break references
400
+ result = self._result
401
+
402
+ def return_result() -> T:
403
+ return result # type: ignore[return-value]
404
+
405
+ self._compute_fn = return_result
406
+
407
+ self._source_released = True
408
+ # Let Python's automatic GC handle cleanup
409
+
410
+ def __repr__(self) -> str:
411
+ """String representation for debugging."""
412
+ status = "computed" if self._computed else "deferred"
413
+ return f"LazyResult(name={self._name!r}, status={status})"
414
+
415
+
416
+ class LazyDict(dict[str, Any]):
417
+ """Dictionary where LazyResult values are auto-evaluated on access.
418
+
419
+ Extends standard dict to automatically evaluate LazyResult values when
420
+ accessed. Regular (non-lazy) values pass through unchanged.
421
+
422
+ Useful for collections of analysis results where some may not be needed.
423
+
424
+ Example:
425
+ >>> results = LazyDict()
426
+ >>> results['fft'] = LazyResult(lambda: np.fft.fft(signal))
427
+ >>> results['power'] = LazyResult(lambda: np.abs(results['fft'])**2)
428
+ >>> results['constant'] = 42 # Non-lazy value
429
+ >>>
430
+ >>> # Access auto-evaluates lazy results
431
+ >>> fft_spectrum = results['fft'] # Computes FFT
432
+ >>> power_spectrum = results['power'] # Computes power
433
+ >>> const = results['constant'] # Returns 42 directly
434
+ >>>
435
+ >>> # Check if computed without triggering computation
436
+ >>> fft_lazy = super(LazyDict, results).__getitem__('fft')
437
+ >>> if fft_lazy.is_computed():
438
+ ... print("FFT already computed")
439
+
440
+ References:
441
+ API-012: Lazy result access patterns
442
+ """
443
+
444
+ def __getitem__(self, key: str) -> Any:
445
+ """Get value, auto-evaluating if it's a LazyResult.
446
+
447
+ Args:
448
+ key: Dictionary key.
449
+
450
+ Returns:
451
+ Evaluated value (LazyResult.value) or raw value.
452
+
453
+ Example:
454
+ >>> lazy_dict = LazyDict()
455
+ >>> lazy_dict['result'] = LazyResult(lambda: expensive_op())
456
+ >>> value = lazy_dict['result'] # Auto-evaluates
457
+ """
458
+ value = super().__getitem__(key)
459
+ if isinstance(value, LazyResult):
460
+ return value.value
461
+ return value
462
+
463
+ def get_lazy(self, key: str) -> LazyResult[Any] | Any:
464
+ """Get the raw value without auto-evaluation.
465
+
466
+ Returns the LazyResult instance itself, not its value.
467
+
468
+ Args:
469
+ key: Dictionary key.
470
+
471
+ Returns:
472
+ Raw value (may be LazyResult instance).
473
+
474
+ Example:
475
+ >>> lazy_dict = LazyDict()
476
+ >>> lazy_dict['result'] = LazyResult(lambda: expensive_op())
477
+ >>> lazy_obj = lazy_dict.get_lazy('result')
478
+ >>> if not lazy_obj.is_computed():
479
+ ... print("Will compute on access")
480
+ """
481
+ return super().__getitem__(key)
482
+
483
+ def is_computed(self, key: str) -> bool:
484
+ """Check if a lazy value has been computed.
485
+
486
+ Args:
487
+ key: Dictionary key.
488
+
489
+ Returns:
490
+ True if value is computed (or not lazy), False otherwise.
491
+
492
+ Example:
493
+ >>> if not lazy_dict.is_computed('fft'):
494
+ ... print("FFT not computed yet")
495
+ """
496
+ value = super().__getitem__(key)
497
+ if isinstance(value, LazyResult):
498
+ return value.is_computed()
499
+ return True # Non-lazy values are "computed"
500
+
501
+ def invalidate(self, key: str) -> None:
502
+ """Invalidate a lazy result, forcing recomputation.
503
+
504
+ Args:
505
+ key: Dictionary key.
506
+
507
+ Example:
508
+ >>> lazy_dict.invalidate('fft')
509
+ >>> # Next access will recompute
510
+ >>> fft = lazy_dict['fft']
511
+ """
512
+ value = super().__getitem__(key)
513
+ if isinstance(value, LazyResult):
514
+ value.invalidate()
515
+
516
+ def invalidate_all(self) -> None:
517
+ """Invalidate all lazy results in the dictionary.
518
+
519
+ Example:
520
+ >>> lazy_dict.invalidate_all()
521
+ >>> # All lazy values will recompute on next access
522
+ """
523
+ for value in self.values():
524
+ if isinstance(value, LazyResult):
525
+ value.invalidate()
526
+
527
+ def computed_keys(self) -> list[str]:
528
+ """Get list of keys with computed values.
529
+
530
+ Returns:
531
+ List of keys whose values are computed.
532
+
533
+ Example:
534
+ >>> computed = lazy_dict.computed_keys()
535
+ >>> print(f"Computed: {computed}")
536
+ """
537
+ return [
538
+ key
539
+ for key, value in super().items()
540
+ if not isinstance(value, LazyResult) or value.is_computed()
541
+ ]
542
+
543
+ def deferred_keys(self) -> list[str]:
544
+ """Get list of keys with deferred (not computed) values.
545
+
546
+ Returns:
547
+ List of keys whose LazyResult values are not computed.
548
+
549
+ Example:
550
+ >>> deferred = lazy_dict.deferred_keys()
551
+ >>> print(f"Not computed: {deferred}")
552
+ """
553
+ return [
554
+ key
555
+ for key, value in super().items()
556
+ if isinstance(value, LazyResult) and not value.is_computed()
557
+ ]
558
+
559
+
560
+ def lazy[T](fn: Callable[..., T]) -> Callable[..., LazyResult[T]]:
561
+ """Decorator to make a function return a LazyResult.
562
+
563
+ Wraps a function so it returns a LazyResult instead of computing
564
+ immediately. Useful for expensive analysis functions.
565
+
566
+ Args:
567
+ fn: Function to wrap.
568
+
569
+ Returns:
570
+ Wrapped function that returns LazyResult.
571
+
572
+ Example:
573
+ >>> @lazy
574
+ ... def compute_fft(signal, nfft):
575
+ ... print("Computing FFT...")
576
+ ... return np.fft.fft(signal, n=nfft)
577
+ >>>
578
+ >>> # Returns LazyResult, doesn't compute yet
579
+ >>> lazy_fft = compute_fft(signal, 8192)
580
+ >>> print("Created lazy result")
581
+ >>>
582
+ >>> # Access triggers computation
583
+ >>> spectrum = lazy_fft.value
584
+ >>> # Prints: "Computing FFT..."
585
+ >>>
586
+ >>> # Second access uses cache
587
+ >>> spectrum2 = lazy_fft.value
588
+ >>> # No print - uses cached result
589
+
590
+ References:
591
+ PERF-002: Lazy evaluation for analysis pipelines
592
+ """
593
+
594
+ @functools.wraps(fn)
595
+ def wrapper(*args: Any, **kwargs: Any) -> LazyResult[T]:
596
+ def compute() -> T:
597
+ return fn(*args, **kwargs)
598
+
599
+ return LazyResult(
600
+ compute,
601
+ name=fn.__name__,
602
+ )
603
+
604
+ return wrapper
605
+
606
+
607
+ class LazyAnalysisResult:
608
+ """Lazy wrapper for multi-domain analysis results.
609
+
610
+ Provides lazy evaluation for analysis engines that produce results across
611
+ multiple domains (time, frequency, statistical, etc.). Only computes
612
+ results for domains that are actually accessed.
613
+
614
+
615
+ Attributes:
616
+ domains: List of available analysis domains.
617
+
618
+ Example:
619
+ >>> # Create analyzer with multiple domains
620
+ >>> analyzer = SignalAnalyzer()
621
+ >>>
622
+ >>> # Wrap in lazy result - nothing computed yet
623
+ >>> lazy_results = LazyAnalysisResult(
624
+ ... analyzer,
625
+ ... signal_data,
626
+ ... domains=['time', 'frequency', 'statistics']
627
+ ... )
628
+ >>>
629
+ >>> # Only compute frequency domain
630
+ >>> freq_results = lazy_results.get_domain('frequency')
631
+ >>> # Time and statistics domains not computed
632
+ >>>
633
+ >>> # Check what's been computed
634
+ >>> print(lazy_results.computed_domains()) # ['frequency']
635
+ >>> print(lazy_results.deferred_domains()) # ['time', 'statistics']
636
+ >>>
637
+ >>> # Access multiple domains
638
+ >>> all_results = lazy_results.compute_all()
639
+
640
+ References:
641
+ PERF-002: Lazy evaluation requirements
642
+ API-012: Multi-domain analysis patterns
643
+ """
644
+
645
+ def __init__(
646
+ self,
647
+ engine: Any,
648
+ data: Any,
649
+ domains: list[str],
650
+ *,
651
+ compute_fn_template: Callable[[Any, Any, str], Any] | None = None,
652
+ ):
653
+ """Initialize lazy analysis result.
654
+
655
+ Args:
656
+ engine: Analysis engine instance.
657
+ data: Input data for analysis.
658
+ domains: List of available analysis domains.
659
+ compute_fn_template: Optional custom compute function.
660
+ Signature: fn(engine, data, domain) -> result.
661
+ Default uses engine.analyze(data, domain=domain).
662
+
663
+ Example:
664
+ >>> lazy_results = LazyAnalysisResult(
665
+ ... my_analyzer,
666
+ ... signal_data,
667
+ ... domains=['time', 'frequency', 'wavelet']
668
+ ... )
669
+ """
670
+ self._engine = engine
671
+ self._data = data
672
+ self.domains = domains
673
+ self._compute_fn = compute_fn_template or self._default_compute
674
+
675
+ # Create lazy results for each domain
676
+ self._domain_results = LazyDict()
677
+ for domain in domains:
678
+
679
+ def make_compute_fn(d: str = domain) -> Callable[[], Any]:
680
+ def compute_domain() -> Any:
681
+ return self._compute_fn(self._engine, self._data, d)
682
+
683
+ return compute_domain
684
+
685
+ self._domain_results[domain] = LazyResult(
686
+ make_compute_fn(),
687
+ name=f"{engine.__class__.__name__}.{domain}",
688
+ )
689
+
690
+ def _default_compute(self, engine: Any, data: Any, domain: str) -> dict[str, Any]:
691
+ """Default compute function for domain analysis.
692
+
693
+ Args:
694
+ engine: Analysis engine.
695
+ data: Input data.
696
+ domain: Domain to analyze.
697
+
698
+ Returns:
699
+ Analysis result for domain.
700
+
701
+ Raises:
702
+ AttributeError: If engine has no analyze() or analyze_{domain}() method.
703
+ """
704
+ # Try common patterns
705
+ if hasattr(engine, "analyze"):
706
+ result: dict[str, Any] = engine.analyze(data, domain=domain)
707
+ return result
708
+ elif hasattr(engine, f"analyze_{domain}"):
709
+ method = getattr(engine, f"analyze_{domain}")
710
+ result = method(data)
711
+ return result
712
+ else:
713
+ raise AttributeError(
714
+ f"Engine {engine.__class__.__name__} has no analyze() method "
715
+ f"or analyze_{domain}() method"
716
+ )
717
+
718
+ def get_domain(self, domain: str) -> Any:
719
+ """Get results for specific domain, computing only that domain.
720
+
721
+ Args:
722
+ domain: Domain name (e.g., 'time', 'frequency').
723
+
724
+ Returns:
725
+ Analysis results for the domain.
726
+
727
+ Raises:
728
+ KeyError: If domain not available.
729
+
730
+ Example:
731
+ >>> freq_results = lazy_results.get_domain('frequency')
732
+ >>> # Only frequency domain computed
733
+
734
+ References:
735
+ PERF-002: Partial evaluation
736
+ """
737
+ if domain not in self.domains:
738
+ raise KeyError(f"Domain '{domain}' not available. Available: {self.domains}")
739
+ return self._domain_results[domain]
740
+
741
+ def computed_domains(self) -> list[str]:
742
+ """Get list of domains that have been computed.
743
+
744
+ Returns:
745
+ List of computed domain names.
746
+
747
+ Example:
748
+ >>> computed = lazy_results.computed_domains()
749
+ >>> print(f"Computed: {computed}")
750
+ """
751
+ return self._domain_results.computed_keys()
752
+
753
+ def deferred_domains(self) -> list[str]:
754
+ """Get list of domains that have not been computed.
755
+
756
+ Returns:
757
+ List of deferred domain names.
758
+
759
+ Example:
760
+ >>> deferred = lazy_results.deferred_domains()
761
+ >>> print(f"Not computed: {deferred}")
762
+ """
763
+ return self._domain_results.deferred_keys()
764
+
765
+ def compute_all(self) -> dict[str, Any]:
766
+ """Compute all domains and return results dictionary.
767
+
768
+ Returns:
769
+ Dictionary mapping domain names to results.
770
+
771
+ Example:
772
+ >>> all_results = lazy_results.compute_all()
773
+ >>> print(all_results.keys()) # All domains
774
+
775
+ References:
776
+ API-012: Bulk computation
777
+ """
778
+ return {domain: self.get_domain(domain) for domain in self.domains}
779
+
780
+ def invalidate_domain(self, domain: str) -> None:
781
+ """Invalidate a specific domain's results.
782
+
783
+ Args:
784
+ domain: Domain to invalidate.
785
+
786
+ Example:
787
+ >>> lazy_results.invalidate_domain('frequency')
788
+ >>> # Next access will recompute
789
+ """
790
+ self._domain_results.invalidate(domain)
791
+
792
+ def invalidate_all(self) -> None:
793
+ """Invalidate all domain results.
794
+
795
+ Example:
796
+ >>> lazy_results.invalidate_all()
797
+ >>> # All domains will recompute on next access
798
+ """
799
+ self._domain_results.invalidate_all()
800
+
801
+ def __getitem__(self, domain: str) -> Any:
802
+ """Dictionary-style access to domains.
803
+
804
+ Args:
805
+ domain: Domain name.
806
+
807
+ Returns:
808
+ Domain results.
809
+
810
+ Example:
811
+ >>> freq_results = lazy_results['frequency']
812
+ """
813
+ return self.get_domain(domain)
814
+
815
+ def __repr__(self) -> str:
816
+ """String representation for debugging."""
817
+ computed = self.computed_domains()
818
+ deferred = self.deferred_domains()
819
+ return (
820
+ f"LazyAnalysisResult(domains={self.domains}, computed={computed}, deferred={deferred})"
821
+ )
822
+
823
+
824
+ __all__ = [
825
+ "LazyAnalysisResult",
826
+ "LazyComputeStats",
827
+ "LazyDict",
828
+ "LazyResult",
829
+ "get_lazy_stats",
830
+ "lazy",
831
+ "reset_lazy_stats",
832
+ ]