oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""L* observation table for DFA learning.
|
|
2
|
+
|
|
3
|
+
This module implements the observation table data structure used in
|
|
4
|
+
Angluin's L* algorithm for active learning of deterministic finite automata.
|
|
5
|
+
|
|
6
|
+
References:
|
|
7
|
+
Angluin, D. (1987). Learning regular sets from queries and counterexamples.
|
|
8
|
+
Information and Computation, 75(2), 87-106.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
from oscura.inference.state_machine import FiniteAutomaton, State, Transition
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ObservationTable:
|
|
20
|
+
"""L* observation table for DFA learning.
|
|
21
|
+
|
|
22
|
+
The observation table is the core data structure in the L* algorithm.
|
|
23
|
+
It maintains:
|
|
24
|
+
- S: Set of prefixes (rows of upper part)
|
|
25
|
+
- SA: Set of S + Σ (one-step extensions, lower part)
|
|
26
|
+
- E: Set of suffixes (columns/experiments)
|
|
27
|
+
- T: Table entries mapping (prefix + suffix) -> bool (accept/reject)
|
|
28
|
+
|
|
29
|
+
A table is:
|
|
30
|
+
- Closed: Every row in SA has an equivalent row in S
|
|
31
|
+
- Consistent: Identical rows in S have identical one-step extensions
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
S: Set of prefixes (represented as tuples of symbols)
|
|
35
|
+
E: Set of suffixes (represented as tuples of symbols)
|
|
36
|
+
T: Mapping from string (prefix+suffix) to acceptance
|
|
37
|
+
alphabet: Alphabet of symbols
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
S: set[tuple[str, ...]] = field(default_factory=lambda: {()})
|
|
41
|
+
E: set[tuple[str, ...]] = field(default_factory=lambda: {()})
|
|
42
|
+
T: dict[tuple[str, ...], bool] = field(default_factory=dict)
|
|
43
|
+
alphabet: set[str] = field(default_factory=set)
|
|
44
|
+
|
|
45
|
+
def row(self, s: tuple[str, ...]) -> tuple[bool, ...]:
|
|
46
|
+
"""Get row for prefix s.
|
|
47
|
+
|
|
48
|
+
Returns the row in the observation table for prefix s, which
|
|
49
|
+
consists of the membership query results for s·e for each suffix e in E.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
s: Prefix (tuple of symbols)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Tuple of boolean values representing the row
|
|
56
|
+
"""
|
|
57
|
+
return tuple(self.T.get(s + e, False) for e in sorted(self.E))
|
|
58
|
+
|
|
59
|
+
def is_closed(self) -> bool:
|
|
60
|
+
"""Check if table is closed.
|
|
61
|
+
|
|
62
|
+
A table is closed if for every row in SA (one-step extensions),
|
|
63
|
+
there exists an equivalent row in S.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if table is closed
|
|
67
|
+
"""
|
|
68
|
+
return self.find_closing_counterexample() is None
|
|
69
|
+
|
|
70
|
+
def is_consistent(self) -> bool:
|
|
71
|
+
"""Check if table is consistent.
|
|
72
|
+
|
|
73
|
+
A table is consistent if for any two prefixes s1, s2 in S with
|
|
74
|
+
identical rows, all one-step extensions s1·a and s2·a also have
|
|
75
|
+
identical rows for each symbol a in the alphabet.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if table is consistent
|
|
79
|
+
"""
|
|
80
|
+
return self.find_consistency_counterexample() is None
|
|
81
|
+
|
|
82
|
+
def find_closing_counterexample(self) -> tuple[str, ...] | None:
|
|
83
|
+
"""Find row in SA without equivalent in S.
|
|
84
|
+
|
|
85
|
+
Searches for a string s·a where s ∈ S and a ∈ Σ such that
|
|
86
|
+
row(s·a) is not equal to row(s') for any s' ∈ S.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
String in SA without equivalent in S, or None if closed
|
|
90
|
+
"""
|
|
91
|
+
# Get all rows in S
|
|
92
|
+
s_rows = {s: self.row(s) for s in self.S}
|
|
93
|
+
|
|
94
|
+
# Check all one-step extensions
|
|
95
|
+
for s in self.S:
|
|
96
|
+
for a in self.alphabet:
|
|
97
|
+
sa = s + (a,)
|
|
98
|
+
sa_row = self.row(sa)
|
|
99
|
+
|
|
100
|
+
# Check if this row exists in S
|
|
101
|
+
if sa_row not in s_rows.values():
|
|
102
|
+
return sa
|
|
103
|
+
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def find_consistency_counterexample(
|
|
107
|
+
self,
|
|
108
|
+
) -> tuple[tuple[str, ...], tuple[str, ...], str] | None:
|
|
109
|
+
"""Find inconsistency in table.
|
|
110
|
+
|
|
111
|
+
Searches for two prefixes s1, s2 in S with identical rows and
|
|
112
|
+
a symbol a such that row(s1·a) ≠ row(s2·a).
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Tuple (s1, s2, a) representing inconsistency, or None if consistent
|
|
116
|
+
"""
|
|
117
|
+
# Build mapping from rows to prefixes
|
|
118
|
+
row_to_prefixes: dict[tuple[bool, ...], list[tuple[str, ...]]] = {}
|
|
119
|
+
for s in self.S:
|
|
120
|
+
r = self.row(s)
|
|
121
|
+
if r not in row_to_prefixes:
|
|
122
|
+
row_to_prefixes[r] = []
|
|
123
|
+
row_to_prefixes[r].append(s)
|
|
124
|
+
|
|
125
|
+
# Check each equivalence class
|
|
126
|
+
for prefixes in row_to_prefixes.values():
|
|
127
|
+
if len(prefixes) < 2:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Check all pairs in this equivalence class
|
|
131
|
+
for i in range(len(prefixes)):
|
|
132
|
+
for j in range(i + 1, len(prefixes)):
|
|
133
|
+
s1 = prefixes[i]
|
|
134
|
+
s2 = prefixes[j]
|
|
135
|
+
|
|
136
|
+
# Check each symbol
|
|
137
|
+
for a in self.alphabet:
|
|
138
|
+
s1a = s1 + (a,)
|
|
139
|
+
s2a = s2 + (a,)
|
|
140
|
+
|
|
141
|
+
if self.row(s1a) != self.row(s2a):
|
|
142
|
+
return (s1, s2, a)
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def to_dfa(self) -> FiniteAutomaton:
|
|
147
|
+
"""Construct hypothesis DFA from closed, consistent table.
|
|
148
|
+
|
|
149
|
+
Creates a DFA where:
|
|
150
|
+
- Each distinct row in S corresponds to a state
|
|
151
|
+
- Transitions are determined by row(s·a)
|
|
152
|
+
- Initial state corresponds to row(ε)
|
|
153
|
+
- Accepting states are those where T[s·ε] = True
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
FiniteAutomaton constructed from table
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If table is not closed or not consistent
|
|
160
|
+
"""
|
|
161
|
+
if not self.is_closed():
|
|
162
|
+
raise ValueError("Table must be closed to construct DFA")
|
|
163
|
+
if not self.is_consistent():
|
|
164
|
+
raise ValueError("Table must be consistent to construct DFA")
|
|
165
|
+
|
|
166
|
+
# Map rows to state IDs
|
|
167
|
+
row_to_state: dict[tuple[bool, ...], int] = {}
|
|
168
|
+
state_to_row: dict[int, tuple[bool, ...]] = {}
|
|
169
|
+
state_representatives: dict[int, tuple[str, ...]] = {}
|
|
170
|
+
|
|
171
|
+
state_id = 0
|
|
172
|
+
for s in sorted(self.S):
|
|
173
|
+
r = self.row(s)
|
|
174
|
+
if r not in row_to_state:
|
|
175
|
+
row_to_state[r] = state_id
|
|
176
|
+
state_to_row[state_id] = r
|
|
177
|
+
state_representatives[state_id] = s
|
|
178
|
+
state_id += 1
|
|
179
|
+
|
|
180
|
+
# Create states
|
|
181
|
+
states = []
|
|
182
|
+
accepting_states = set()
|
|
183
|
+
|
|
184
|
+
for sid in range(state_id):
|
|
185
|
+
rep = state_representatives[sid]
|
|
186
|
+
# State is accepting if T[rep] = True (i.e., rep is accepted)
|
|
187
|
+
is_accepting = self.T.get(rep, False)
|
|
188
|
+
is_initial = rep == ()
|
|
189
|
+
|
|
190
|
+
state = State(
|
|
191
|
+
id=sid,
|
|
192
|
+
name=f"q{sid}",
|
|
193
|
+
is_initial=is_initial,
|
|
194
|
+
is_accepting=is_accepting,
|
|
195
|
+
)
|
|
196
|
+
states.append(state)
|
|
197
|
+
|
|
198
|
+
if is_accepting:
|
|
199
|
+
accepting_states.add(sid)
|
|
200
|
+
|
|
201
|
+
# Create transitions
|
|
202
|
+
transitions = []
|
|
203
|
+
seen_transitions = set()
|
|
204
|
+
|
|
205
|
+
for sid in range(state_id):
|
|
206
|
+
rep = state_representatives[sid]
|
|
207
|
+
|
|
208
|
+
for a in sorted(self.alphabet):
|
|
209
|
+
# Find target state
|
|
210
|
+
rep_a = rep + (a,)
|
|
211
|
+
target_row = self.row(rep_a)
|
|
212
|
+
target_state = row_to_state[target_row]
|
|
213
|
+
|
|
214
|
+
# Add transition
|
|
215
|
+
key = (sid, target_state, a)
|
|
216
|
+
if key not in seen_transitions:
|
|
217
|
+
seen_transitions.add(key)
|
|
218
|
+
transitions.append(Transition(source=sid, target=target_state, symbol=a))
|
|
219
|
+
|
|
220
|
+
# Find initial state
|
|
221
|
+
initial_row = self.row(())
|
|
222
|
+
initial_state = row_to_state[initial_row]
|
|
223
|
+
|
|
224
|
+
return FiniteAutomaton(
|
|
225
|
+
states=states,
|
|
226
|
+
transitions=transitions,
|
|
227
|
+
alphabet=self.alphabet.copy(),
|
|
228
|
+
initial_state=initial_state,
|
|
229
|
+
accepting_states=accepting_states,
|
|
230
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Oracle interface for L* active learning.
|
|
2
|
+
|
|
3
|
+
This module defines the oracle interface used in the L* algorithm.
|
|
4
|
+
The oracle answers membership and equivalence queries about the target DFA.
|
|
5
|
+
|
|
6
|
+
References:
|
|
7
|
+
Angluin, D. (1987). Learning regular sets from queries and counterexamples.
|
|
8
|
+
Information and Computation, 75(2), 87-106.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
|
|
15
|
+
from oscura.inference.state_machine import FiniteAutomaton # noqa: TC001
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Oracle(ABC):
|
|
19
|
+
"""Oracle for answering membership and equivalence queries.
|
|
20
|
+
|
|
21
|
+
An oracle provides access to the target language through two types of queries:
|
|
22
|
+
|
|
23
|
+
1. Membership Query: "Is word w accepted by the target DFA?"
|
|
24
|
+
- Returns True if w is in the target language, False otherwise
|
|
25
|
+
- Must be consistent (same query always returns same answer)
|
|
26
|
+
|
|
27
|
+
2. Equivalence Query: "Is hypothesis H equivalent to the target DFA?"
|
|
28
|
+
- Returns None if H is equivalent to target
|
|
29
|
+
- Returns a counterexample word if H differs from target
|
|
30
|
+
- Counterexample is a word accepted by exactly one of H or target
|
|
31
|
+
|
|
32
|
+
The oracle also provides the alphabet of the target language.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def membership_query(self, word: tuple[str, ...]) -> bool:
|
|
37
|
+
"""Answer membership query: Is word accepted by target DFA?
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
word: Sequence of symbols to test
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if word is accepted by target language, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def equivalence_query(self, hypothesis: FiniteAutomaton) -> tuple[str, ...] | None:
|
|
49
|
+
"""Answer equivalence query: Is hypothesis equivalent to target?
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
hypothesis: Proposed DFA to test for equivalence
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
None if hypothesis is equivalent to target, otherwise a
|
|
56
|
+
counterexample word that is accepted by exactly one of
|
|
57
|
+
hypothesis or target
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_alphabet(self) -> set[str]:
|
|
63
|
+
"""Get the alphabet of the target language.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Set of symbols in the target alphabet
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def get_query_counts(self) -> tuple[int, int]:
|
|
71
|
+
"""Get counts of membership and equivalence queries.
|
|
72
|
+
|
|
73
|
+
This is useful for analyzing the efficiency of the learning algorithm.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Tuple of (membership_queries, equivalence_queries)
|
|
77
|
+
"""
|
|
78
|
+
return (0, 0) # Default implementation, can be overridden
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Teachers for L* active learning.
|
|
2
|
+
|
|
3
|
+
This module provides different oracle implementations (teachers) for the
|
|
4
|
+
L* algorithm. Teachers can be based on:
|
|
5
|
+
|
|
6
|
+
- Simulator: Replay from captured protocol traces
|
|
7
|
+
- Interactive: Live device interaction
|
|
8
|
+
- Model: From formal protocol specification
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from oscura.inference.active_learning.teachers.simulator import SimulatorTeacher
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SimulatorTeacher",
|
|
15
|
+
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Simulator teacher for L* active learning.
|
|
2
|
+
|
|
3
|
+
This module implements an oracle that uses captured protocol traces as
|
|
4
|
+
ground truth. It's useful for learning DFAs from historical data without
|
|
5
|
+
requiring a live system.
|
|
6
|
+
|
|
7
|
+
The simulator teacher treats traces as examples of valid protocol sequences.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from oscura.inference.active_learning.oracle import Oracle
|
|
13
|
+
from oscura.inference.state_machine import FiniteAutomaton # noqa: TC001
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SimulatorTeacher(Oracle):
|
|
17
|
+
"""Oracle that replays captured protocol traces.
|
|
18
|
+
|
|
19
|
+
This teacher uses captured message traces as ground truth for learning.
|
|
20
|
+
It answers queries based on whether sequences appear in the captured traces.
|
|
21
|
+
|
|
22
|
+
Membership queries check if a word is a valid prefix of any trace.
|
|
23
|
+
Equivalence queries test the hypothesis against all captured traces.
|
|
24
|
+
|
|
25
|
+
This is useful for:
|
|
26
|
+
- Learning from historical protocol captures
|
|
27
|
+
- Offline protocol analysis
|
|
28
|
+
- Testing L* implementation with known data
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
traces: List of message sequences (each trace is list of symbols)
|
|
32
|
+
alphabet: Set of all symbols appearing in traces
|
|
33
|
+
membership_query_count: Number of membership queries answered
|
|
34
|
+
equivalence_query_count: Number of equivalence queries answered
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, traces: list[list[str]]):
|
|
38
|
+
"""Initialize simulator teacher from captured traces.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
traces: List of message sequences, where each sequence is a
|
|
42
|
+
list of symbols (strings). All traces are treated as
|
|
43
|
+
positive examples (accepted sequences).
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If no traces provided
|
|
47
|
+
"""
|
|
48
|
+
if not traces:
|
|
49
|
+
raise ValueError("Need at least one trace")
|
|
50
|
+
|
|
51
|
+
self.traces = traces
|
|
52
|
+
self.alphabet = self._extract_alphabet()
|
|
53
|
+
self.membership_query_count = 0
|
|
54
|
+
self.equivalence_query_count = 0
|
|
55
|
+
|
|
56
|
+
# Build set of all valid prefixes for efficient membership queries
|
|
57
|
+
self._valid_prefixes: set[tuple[str, ...]] = set()
|
|
58
|
+
self._build_prefix_set()
|
|
59
|
+
|
|
60
|
+
def _extract_alphabet(self) -> set[str]:
|
|
61
|
+
"""Extract alphabet from all traces.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Set of all unique symbols in traces
|
|
65
|
+
"""
|
|
66
|
+
alphabet = set()
|
|
67
|
+
for trace in self.traces:
|
|
68
|
+
alphabet.update(trace)
|
|
69
|
+
return alphabet
|
|
70
|
+
|
|
71
|
+
def _build_prefix_set(self) -> None:
|
|
72
|
+
"""Build set of all valid prefixes from traces.
|
|
73
|
+
|
|
74
|
+
Pre-computes all prefixes for efficient membership query answering.
|
|
75
|
+
"""
|
|
76
|
+
for trace in self.traces:
|
|
77
|
+
# Add empty prefix
|
|
78
|
+
self._valid_prefixes.add(())
|
|
79
|
+
|
|
80
|
+
# Add all prefixes of this trace
|
|
81
|
+
for i in range(1, len(trace) + 1):
|
|
82
|
+
prefix = tuple(trace[:i])
|
|
83
|
+
self._valid_prefixes.add(prefix)
|
|
84
|
+
|
|
85
|
+
def membership_query(self, word: tuple[str, ...]) -> bool:
|
|
86
|
+
"""Check if word is a valid prefix of any trace.
|
|
87
|
+
|
|
88
|
+
A word is accepted if it appears as a prefix (including the full
|
|
89
|
+
sequence) of at least one captured trace.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
word: Sequence of symbols to test
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if word is a prefix of any trace, False otherwise
|
|
96
|
+
"""
|
|
97
|
+
self.membership_query_count += 1
|
|
98
|
+
return word in self._valid_prefixes
|
|
99
|
+
|
|
100
|
+
def equivalence_query(self, hypothesis: FiniteAutomaton) -> tuple[str, ...] | None:
|
|
101
|
+
"""Check hypothesis against all traces.
|
|
102
|
+
|
|
103
|
+
Tests whether the hypothesis DFA correctly accepts all captured traces
|
|
104
|
+
and their prefixes. Returns a counterexample if the hypothesis disagrees
|
|
105
|
+
with the trace data.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
hypothesis: Proposed DFA to test
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
None if hypothesis matches all trace data, otherwise a
|
|
112
|
+
counterexample word that hypothesis classifies incorrectly
|
|
113
|
+
"""
|
|
114
|
+
self.equivalence_query_count += 1
|
|
115
|
+
|
|
116
|
+
# Check all valid prefixes
|
|
117
|
+
for prefix in self._valid_prefixes:
|
|
118
|
+
hypothesis_accepts = hypothesis.accepts(list(prefix))
|
|
119
|
+
target_accepts = True # All prefixes should be accepted
|
|
120
|
+
|
|
121
|
+
if hypothesis_accepts != target_accepts:
|
|
122
|
+
return prefix
|
|
123
|
+
|
|
124
|
+
# Also check some strings not in traces (they should be rejected)
|
|
125
|
+
# Generate some random invalid sequences
|
|
126
|
+
invalid_sequences = self._generate_invalid_sequences(hypothesis)
|
|
127
|
+
|
|
128
|
+
for seq in invalid_sequences:
|
|
129
|
+
hypothesis_accepts = hypothesis.accepts(list(seq))
|
|
130
|
+
target_accepts = seq in self._valid_prefixes
|
|
131
|
+
|
|
132
|
+
if hypothesis_accepts != target_accepts:
|
|
133
|
+
return seq
|
|
134
|
+
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _generate_invalid_sequences(
|
|
138
|
+
self, hypothesis: FiniteAutomaton, max_checks: int = 100
|
|
139
|
+
) -> list[tuple[str, ...]]:
|
|
140
|
+
"""Generate sequences not in traces to test hypothesis.
|
|
141
|
+
|
|
142
|
+
Creates test sequences by:
|
|
143
|
+
1. Taking prefixes from traces and extending with different symbols
|
|
144
|
+
2. Creating short random sequences
|
|
145
|
+
3. Concatenating partial traces
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
hypothesis: Current hypothesis (used to guide generation)
|
|
149
|
+
max_checks: Maximum number of invalid sequences to generate
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of sequences not in valid prefixes
|
|
153
|
+
"""
|
|
154
|
+
invalid = []
|
|
155
|
+
|
|
156
|
+
# Strategy 1: Extend valid prefixes with unexpected symbols
|
|
157
|
+
for prefix in list(self._valid_prefixes)[:20]: # Limit to avoid too many
|
|
158
|
+
for symbol in self.alphabet:
|
|
159
|
+
candidate = prefix + (symbol,)
|
|
160
|
+
if candidate not in self._valid_prefixes:
|
|
161
|
+
invalid.append(candidate)
|
|
162
|
+
if len(invalid) >= max_checks:
|
|
163
|
+
return invalid
|
|
164
|
+
|
|
165
|
+
# Strategy 2: Create sequences from alphabet permutations
|
|
166
|
+
if len(self.alphabet) > 0:
|
|
167
|
+
alphabet_list = list(self.alphabet)
|
|
168
|
+
for i in range(min(5, len(alphabet_list))):
|
|
169
|
+
for j in range(min(5, len(alphabet_list))):
|
|
170
|
+
candidate = (alphabet_list[i], alphabet_list[j])
|
|
171
|
+
if candidate not in self._valid_prefixes:
|
|
172
|
+
invalid.append(candidate)
|
|
173
|
+
if len(invalid) >= max_checks:
|
|
174
|
+
return invalid
|
|
175
|
+
|
|
176
|
+
return invalid
|
|
177
|
+
|
|
178
|
+
def get_alphabet(self) -> set[str]:
|
|
179
|
+
"""Get the alphabet of the target language.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Set of symbols extracted from traces
|
|
183
|
+
"""
|
|
184
|
+
return self.alphabet.copy()
|
|
185
|
+
|
|
186
|
+
def get_query_counts(self) -> tuple[int, int]:
|
|
187
|
+
"""Get counts of membership and equivalence queries.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Tuple of (membership_queries, equivalence_queries)
|
|
191
|
+
"""
|
|
192
|
+
return (self.membership_query_count, self.equivalence_query_count)
|