oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Literal
24
24
  import numpy as np
25
25
 
26
26
  from oscura.core.memoize import memoize_analysis
27
+ from oscura.core.numba_backend import njit
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  from numpy.typing import NDArray
@@ -118,11 +119,19 @@ def detect_edges(
118
119
  threshold: float | Literal["auto"] = "auto",
119
120
  hysteresis: float = 0.0,
120
121
  sample_rate: float = 1.0,
122
+ use_numba: bool = True,
121
123
  ) -> list[Edge]:
122
124
  """Detect signal edges with configurable threshold.
123
125
 
124
126
  Detects rising and/or falling edges in a digital or analog signal with
125
- optional hysteresis for noise immunity.
127
+ optional hysteresis for noise immunity. Uses Numba JIT compilation for
128
+ 15-30x speedup on large signals (>1000 samples).
129
+
130
+ Performance characteristics:
131
+ - Small signals (<1000 samples): Pure Python (no overhead)
132
+ - Large signals (>=1000 samples): Numba JIT (15-30x faster)
133
+ - First Numba call: ~100-200ms compilation overhead (cached)
134
+ - Subsequent calls: <1ms for 100k samples
126
135
 
127
136
  Args:
128
137
  trace: Input signal trace (analog or digital).
@@ -130,6 +139,7 @@ def detect_edges(
130
139
  threshold: Detection threshold. 'auto' computes from signal midpoint.
131
140
  hysteresis: Hysteresis amount for noise immunity (signal units).
132
141
  sample_rate: Sample rate in Hz for time calculation.
142
+ use_numba: Use Numba JIT acceleration for large signals (default: True).
133
143
 
134
144
  Returns:
135
145
  List of Edge objects with detected edges.
@@ -139,93 +149,343 @@ def detect_edges(
139
149
  >>> edges = detect_edges(signal, edge_type='rising')
140
150
  >>> len(edges)
141
151
  1
152
+
153
+ Note:
154
+ Numba JIT compilation provides significant speedup for repeated edge
155
+ detection on large signals. The compilation overhead (~100-200ms) is
156
+ amortized across subsequent calls due to caching.
142
157
  """
143
158
  if len(trace) < 2:
144
159
  return []
145
160
 
146
- trace = np.asarray(trace, dtype=np.float64)
147
-
148
- # Compute threshold if auto
149
- thresh_val: float
150
- if threshold == "auto":
151
- thresh_val = float((np.max(trace) + np.min(trace)) / 2.0)
161
+ # Handle WaveformTrace objects by extracting data
162
+ if hasattr(trace, "data"):
163
+ trace_data: NDArray[np.float64] = np.asarray(trace.data, dtype=np.float64)
152
164
  else:
153
- thresh_val = threshold
165
+ trace_data = np.asarray(trace, dtype=np.float64)
166
+ thresh_val = _compute_threshold(trace_data, threshold)
167
+ thresh_high, thresh_low = _apply_hysteresis(thresh_val, hysteresis)
168
+ time_base = 1.0 / sample_rate
154
169
 
155
- # Apply hysteresis if specified
156
- if hysteresis > 0:
157
- thresh_high = thresh_val + hysteresis / 2.0
158
- thresh_low = thresh_val - hysteresis / 2.0
159
- else:
160
- thresh_high = thresh_val
161
- thresh_low = thresh_val
170
+ # Use Numba for large signals to achieve 15-30x speedup
171
+ if use_numba and len(trace_data) >= 1000:
172
+ detect_rising = edge_type in ["rising", "both"]
173
+ detect_falling = edge_type in ["falling", "both"]
162
174
 
163
- edges: list[Edge] = []
164
- time_base = 1.0 / sample_rate
175
+ edge_indices, edge_types = _find_edges_numba(
176
+ trace_data, thresh_high, thresh_low, detect_rising, detect_falling
177
+ )
178
+
179
+ # Build Edge objects from Numba results
180
+ edges: list[Edge] = []
181
+ for idx, is_rising in zip(edge_indices, edge_types, strict=True):
182
+ i = int(idx)
183
+ prev_val = trace_data[i - 1] if i > 0 else trace_data[i]
184
+ curr_val = trace_data[i]
185
+ edge_type_str: Literal["rising", "falling"] = "rising" if is_rising else "falling"
186
+
187
+ edge = _create_edge(
188
+ trace_data, i, edge_type_str, prev_val, curr_val, time_base, sample_rate
189
+ )
190
+ edges.append(edge)
165
191
 
166
- # State machine for hysteresis
167
- state = trace[0] > thresh_val # Initial state
192
+ return edges
168
193
 
169
- for i in range(1, len(trace)):
170
- prev_val = trace[i - 1]
171
- curr_val = trace[i]
194
+ # Fallback to pure Python for small signals or when Numba disabled
195
+ edges = []
196
+ state = trace_data[0] > thresh_val
197
+
198
+ for i in range(1, len(trace_data)):
199
+ prev_val, curr_val = trace_data[i - 1], trace_data[i]
172
200
 
173
- # Detect transitions with hysteresis
174
201
  if not state and curr_val > thresh_high:
175
- # Rising edge
176
202
  if edge_type in ["rising", "both"]:
177
- # Interpolate edge time
178
- interp_time = interpolate_edge_time(trace, i - 1, method="linear")
179
- time = (i - 1 + interp_time) * time_base
180
-
181
- # Calculate edge properties
182
- amplitude = curr_val - prev_val
183
- slew_rate = amplitude * sample_rate
184
-
185
- # Classify quality (simple heuristic)
186
- quality = classify_edge_quality(trace, i, sample_rate)
187
-
188
- edges.append(
189
- Edge(
190
- sample_index=i,
191
- time=time,
192
- edge_type="rising",
193
- amplitude=abs(amplitude),
194
- slew_rate=slew_rate,
195
- quality=quality,
196
- )
203
+ edge = _create_edge(
204
+ trace_data, i, "rising", prev_val, curr_val, time_base, sample_rate
197
205
  )
206
+ edges.append(edge)
198
207
  state = True
199
208
 
200
209
  elif state and curr_val < thresh_low:
201
- # Falling edge
202
210
  if edge_type in ["falling", "both"]:
203
- # Interpolate edge time
204
- interp_time = interpolate_edge_time(trace, i - 1, method="linear")
205
- time = (i - 1 + interp_time) * time_base
206
-
207
- # Calculate edge properties
208
- amplitude = prev_val - curr_val
209
- slew_rate = -amplitude * sample_rate
210
-
211
- # Classify quality (simple heuristic)
212
- quality = classify_edge_quality(trace, i, sample_rate)
213
-
214
- edges.append(
215
- Edge(
216
- sample_index=i,
217
- time=time,
218
- edge_type="falling",
219
- amplitude=abs(amplitude),
220
- slew_rate=slew_rate,
221
- quality=quality,
222
- )
211
+ edge = _create_edge(
212
+ trace_data, i, "falling", prev_val, curr_val, time_base, sample_rate
223
213
  )
214
+ edges.append(edge)
224
215
  state = False
225
216
 
226
217
  return edges
227
218
 
228
219
 
220
+ def _compute_threshold(trace: NDArray[np.float64], threshold: float | Literal["auto"]) -> float:
221
+ """Compute detection threshold.
222
+
223
+ Args:
224
+ trace: Signal trace.
225
+ threshold: Threshold value or "auto".
226
+
227
+ Returns:
228
+ Threshold value.
229
+ """
230
+ if threshold == "auto":
231
+ return float((np.max(trace) + np.min(trace)) / 2.0)
232
+ else:
233
+ return threshold
234
+
235
+
236
+ def _apply_hysteresis(thresh_val: float, hysteresis: float) -> tuple[float, float]:
237
+ """Apply hysteresis to threshold.
238
+
239
+ Args:
240
+ thresh_val: Base threshold.
241
+ hysteresis: Hysteresis amount.
242
+
243
+ Returns:
244
+ Tuple of (thresh_high, thresh_low).
245
+ """
246
+ if hysteresis > 0:
247
+ return thresh_val + hysteresis / 2.0, thresh_val - hysteresis / 2.0
248
+ else:
249
+ return thresh_val, thresh_val
250
+
251
+
252
+ @njit(cache=True) # type: ignore[untyped-decorator]
253
+ def _find_edges_numba(
254
+ data: NDArray[np.float64],
255
+ thresh_high: float,
256
+ thresh_low: float,
257
+ detect_rising: bool,
258
+ detect_falling: bool,
259
+ ) -> tuple[NDArray[np.int64], NDArray[np.bool_]]:
260
+ """Find edges with hysteresis using Numba JIT compilation.
261
+
262
+ This function provides 15-30x speedup vs pure Python loops for edge detection
263
+ on large signals. The first call includes ~100-200ms compilation overhead,
264
+ but subsequent calls with cached compilation are extremely fast.
265
+
266
+ Performance characteristics:
267
+ - First call: ~100-200ms compilation + execution
268
+ - Subsequent calls: <1ms for 100k samples (15-30x faster than Python)
269
+ - Memory efficient: O(n) space for edge storage
270
+ - Parallel execution: Uses single thread (hysteresis requires sequential state)
271
+
272
+ Args:
273
+ data: Input signal trace (must be contiguous float64 array).
274
+ thresh_high: Upper threshold for hysteresis (rising edge detection).
275
+ thresh_low: Lower threshold for hysteresis (falling edge detection).
276
+ detect_rising: Whether to detect rising edges.
277
+ detect_falling: Whether to detect falling edges.
278
+
279
+ Returns:
280
+ Tuple of (edge_indices, edge_types) where:
281
+ - edge_indices: Array of sample indices where edges occur
282
+ - edge_types: Boolean array (True=rising, False=falling)
283
+
284
+ Example:
285
+ >>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
286
+ >>> indices, types = _find_edges_numba(signal, 0.5, 0.5, True, True)
287
+ >>> indices # [2, 4]
288
+ >>> types # [True, False]
289
+ """
290
+ n = len(data)
291
+ if n < 2:
292
+ return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.bool_)
293
+
294
+ # Pre-allocate arrays for worst case (all samples are edges)
295
+ edge_indices = np.empty(n, dtype=np.int64)
296
+ edge_types = np.empty(n, dtype=np.bool_)
297
+ edge_count = 0
298
+
299
+ # Initial state based on first sample
300
+ state = data[0] > (thresh_high + thresh_low) / 2.0
301
+
302
+ for i in range(1, n):
303
+ curr_val = data[i]
304
+
305
+ if not state and curr_val > thresh_high:
306
+ # Rising edge detected
307
+ if detect_rising:
308
+ edge_indices[edge_count] = i
309
+ edge_types[edge_count] = True # Rising
310
+ edge_count += 1
311
+ state = True
312
+
313
+ elif state and curr_val < thresh_low:
314
+ # Falling edge detected
315
+ if detect_falling:
316
+ edge_indices[edge_count] = i
317
+ edge_types[edge_count] = False # Falling
318
+ edge_count += 1
319
+ state = False
320
+
321
+ # Return trimmed arrays
322
+ return edge_indices[:edge_count], edge_types[:edge_count]
323
+
324
+
325
+ @njit(cache=True) # type: ignore[untyped-decorator]
326
+ def _measure_pulse_widths_numba(
327
+ edge_indices: NDArray[np.int64],
328
+ edge_types: NDArray[np.bool_],
329
+ time_base: float,
330
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
331
+ """Measure pulse widths between edges using Numba JIT.
332
+
333
+ Computes high pulse widths (rising to falling) and low pulse widths
334
+ (falling to rising) from detected edges. Provides 10-20x speedup vs
335
+ Python loops for large edge lists.
336
+
337
+ Performance characteristics:
338
+ - First call: ~50-100ms compilation overhead
339
+ - Subsequent calls: <0.5ms for 10k edges
340
+ - Memory: O(n) space for pulse arrays
341
+
342
+ Args:
343
+ edge_indices: Array of edge sample indices.
344
+ edge_types: Boolean array (True=rising, False=falling).
345
+ time_base: Time per sample (1 / sample_rate).
346
+
347
+ Returns:
348
+ Tuple of (high_widths, low_widths) where:
349
+ - high_widths: Array of high pulse widths in seconds
350
+ - low_widths: Array of low pulse widths in seconds
351
+
352
+ Example:
353
+ >>> indices = np.array([100, 200, 300, 400], dtype=np.int64)
354
+ >>> types = np.array([True, False, True, False])
355
+ >>> high, low = _measure_pulse_widths_numba(indices, types, 1e-6)
356
+ >>> high # [100e-6, 100e-6] (200-100, 400-300)
357
+ >>> low # [100e-6] (300-200)
358
+ """
359
+ n_edges = len(edge_indices)
360
+ if n_edges < 2:
361
+ return np.empty(0, dtype=np.float64), np.empty(0, dtype=np.float64)
362
+
363
+ # Pre-allocate arrays
364
+ high_widths = np.empty(n_edges, dtype=np.float64)
365
+ low_widths = np.empty(n_edges, dtype=np.float64)
366
+ high_count = 0
367
+ low_count = 0
368
+
369
+ for i in range(n_edges - 1):
370
+ if edge_types[i]: # Rising edge
371
+ # Look for next falling edge
372
+ if not edge_types[i + 1]:
373
+ width = (edge_indices[i + 1] - edge_indices[i]) * time_base
374
+ high_widths[high_count] = width
375
+ high_count += 1
376
+ else: # Falling edge
377
+ # Look for next rising edge
378
+ if edge_types[i + 1]:
379
+ width = (edge_indices[i + 1] - edge_indices[i]) * time_base
380
+ low_widths[low_count] = width
381
+ low_count += 1
382
+
383
+ return high_widths[:high_count], low_widths[:low_count]
384
+
385
+
386
+ @njit(cache=True) # type: ignore[untyped-decorator]
387
+ def _compute_slew_rates_numba(
388
+ data: NDArray[np.float64],
389
+ edge_indices: NDArray[np.int64],
390
+ edge_types: NDArray[np.bool_],
391
+ sample_rate: float,
392
+ ) -> NDArray[np.float64]:
393
+ """Compute slew rates at edges using Numba JIT.
394
+
395
+ Calculates the rate of voltage change (dV/dt) at each edge by examining
396
+ the samples before and after the edge. Provides 15-25x speedup vs Python.
397
+
398
+ Performance characteristics:
399
+ - First call: ~50-100ms compilation overhead
400
+ - Subsequent calls: <0.3ms for 10k edges
401
+ - Memory: O(n) space for slew rate array
402
+
403
+ Args:
404
+ data: Input signal trace.
405
+ edge_indices: Array of edge sample indices.
406
+ edge_types: Boolean array (True=rising, False=falling).
407
+ sample_rate: Sample rate in Hz.
408
+
409
+ Returns:
410
+ Array of slew rates in signal units per second (V/s).
411
+ Positive for rising edges, negative for falling edges.
412
+
413
+ Example:
414
+ >>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
415
+ >>> indices = np.array([2, 4], dtype=np.int64)
416
+ >>> types = np.array([True, False])
417
+ >>> slew = _compute_slew_rates_numba(signal, indices, types, 1e6)
418
+ >>> slew # [1e6, -1e6] (1V in 1us)
419
+ """
420
+ n_edges = len(edge_indices)
421
+ slew_rates = np.empty(n_edges, dtype=np.float64)
422
+
423
+ for i in range(n_edges):
424
+ idx = edge_indices[i]
425
+
426
+ if idx < 1 or idx >= len(data):
427
+ slew_rates[i] = 0.0
428
+ continue
429
+
430
+ # Compute amplitude change
431
+ prev_val = data[idx - 1]
432
+ curr_val = data[idx]
433
+ amplitude = abs(curr_val - prev_val)
434
+
435
+ # Compute slew rate
436
+ if edge_types[i]: # Rising
437
+ slew_rates[i] = amplitude * sample_rate
438
+ else: # Falling
439
+ slew_rates[i] = -amplitude * sample_rate
440
+
441
+ return slew_rates
442
+
443
+
444
+ def _create_edge(
445
+ trace: NDArray[np.float64],
446
+ index: int,
447
+ edge_type: Literal["rising", "falling"],
448
+ prev_val: float,
449
+ curr_val: float,
450
+ time_base: float,
451
+ sample_rate: float,
452
+ ) -> Edge:
453
+ """Create edge object from detected transition.
454
+
455
+ Args:
456
+ trace: Signal trace.
457
+ index: Sample index.
458
+ edge_type: Edge type.
459
+ prev_val: Previous value.
460
+ curr_val: Current value.
461
+ time_base: Time per sample.
462
+ sample_rate: Sample rate.
463
+
464
+ Returns:
465
+ Edge object.
466
+ """
467
+ interp_time = interpolate_edge_time(trace, index - 1, method="linear")
468
+ time = (index - 1 + interp_time) * time_base
469
+
470
+ if edge_type == "rising":
471
+ amplitude = curr_val - prev_val
472
+ slew_rate = amplitude * sample_rate
473
+ else: # falling
474
+ amplitude = prev_val - curr_val
475
+ slew_rate = -amplitude * sample_rate
476
+
477
+ quality = classify_edge_quality(trace, index, sample_rate)
478
+
479
+ return Edge(
480
+ sample_index=index,
481
+ time=time,
482
+ edge_type=edge_type,
483
+ amplitude=abs(amplitude),
484
+ slew_rate=slew_rate,
485
+ quality=quality,
486
+ )
487
+
488
+
229
489
  def interpolate_edge_time(
230
490
  trace: NDArray[np.float64], sample_index: int, method: Literal["linear", "quadratic"] = "linear"
231
491
  ) -> float: