Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e41ac82
reimplement data extension for GSM (rebase was too tedious)
zwergziege Sep 22, 2025
4ce915b
Added init function to datahandler
zwergziege Oct 13, 2025
6bd7f76
FIX: also use abstraction on initial output in GSM + Data
zwergziege Oct 23, 2025
5270972
Allow IOHandler.aggregate to modify src and dst data. Also aggregate …
zwergziege Oct 23, 2025
b705ac4
added tiny todo
zwergziege Oct 24, 2025
18c4733
minor change in data aggregation
zwergziege Nov 3, 2025
03c0c35
type hints for data
zwergziege Jun 25, 2026
2c34595
Merge branch 'master' into gsm_data_extension
zwergziege Jun 25, 2026
2fa02c2
intersection iterator: add option to iterate over smaller
BenjaminVonBerg Jul 3, 2026
ab5f8da
minor cleanup
BenjaminVonBerg Jul 3, 2026
5876e30
add options for input completion
BenjaminVonBerg Jul 3, 2026
20db16c
extract IOHandlers in own file
BenjaminVonBerg Jul 3, 2026
0420283
add IOHandler callback for new merge computation
BenjaminVonBerg Jul 3, 2026
282c60f
add copy on write IOHandler
BenjaminVonBerg Jul 3, 2026
0cdaaf0
added merges per second to ProgressReport
BenjaminVonBerg Jul 6, 2026
08eab98
wip commit
BenjaminVonBerg Jul 3, 2026
ca4bfef
minor fixes
BenjaminVonBerg Jul 16, 2026
eb8cd64
add simplified version of GsmNode.transition_iterator
BenjaminVonBerg Jul 8, 2026
ab4deac
slight improvement for blue state computation
BenjaminVonBerg Jul 8, 2026
9096343
lazy copy transition info during merge creation
BenjaminVonBerg Jul 16, 2026
e4759e4
eliminated explicit blue state construction
BenjaminVonBerg Jul 16, 2026
ebe9cb8
less comparisons for moore machines
BenjaminVonBerg Jul 16, 2026
79de36b
minor fixes
BenjaminVonBerg Jul 20, 2026
5f210af
introduced two phase merging to GSM
BenjaminVonBerg Jul 21, 2026
f1f55dd
shift responsibility to ensure determinism to user (with sensible def…
BenjaminVonBerg Jul 21, 2026
c4dd597
add option to rank candidates for promotion in GSM
BenjaminVonBerg Jul 21, 2026
ebb6c17
eliminate determinism check from score calculation for comparing futu…
BenjaminVonBerg Jul 21, 2026
e43e236
fix learning moore machines from examples
BenjaminVonBerg Jul 21, 2026
fe82071
switch from special object to none for deferring merges
BenjaminVonBerg Jul 21, 2026
797102f
keep track of number of merges in all cases
BenjaminVonBerg Jul 21, 2026
63608e4
eliminate use_early_verdicts argument
BenjaminVonBerg Jul 21, 2026
e941175
speed up count computation
BenjaminVonBerg Jul 22, 2026
3d5b516
marginally simplify hoeffding compatibility
BenjaminVonBerg Jul 22, 2026
7ce5a9d
add simple edsm mode to check based on futures
BenjaminVonBerg Jul 22, 2026
89c717e
avoid repeated creation of sentinel object
BenjaminVonBerg Jul 22, 2026
9262acb
avoid spurious tuple construction
BenjaminVonBerg Jul 22, 2026
5c90a62
restore sensible gsm default for learning stochastic systems
BenjaminVonBerg Jul 22, 2026
9277370
fix and clarify default node order. also changed node order interface…
BenjaminVonBerg Jul 23, 2026
6db97e1
fix corner case in local compat (probably not relevant yet)
BenjaminVonBerg Aug 3, 2026
14e5e25
Merge remote-tracking branch 'upstream/master' into upstream-pr
BenjaminVonBerg Sep 1, 2026
327b5f0
minor: fix doc-strings and simplify logic
BenjaminVonBerg Sep 1, 2026
23a2d3f
added explicit values for reject and greedy accept scores (instead of…
BenjaminVonBerg Sep 1, 2026
e05fe3d
eliminate edsm from CheckFutureScore
BenjaminVonBerg Sep 2, 2026
ecd63e7
eliminate unused code
BenjaminVonBerg Sep 4, 2026
3a099cf
docstrings, fixing examples and some refactoring
BenjaminVonBerg Sep 4, 2026
2460dde
Rename IOHandler -> DataHandler
BenjaminVonBerg Sep 4, 2026
5d7511d
in SimpleFutureBasedScore, added option to not check compatibility of…
BenjaminVonBerg Sep 7, 2026
f8ddfa1
Added dedicated `SimpleScoreCalculation` to eliminate the hack in `Sc…
BenjaminVonBerg Sep 7, 2026
117212d
added special value for input of root GsmNode objects
BenjaminVonBerg Sep 8, 2026
5659764
eliminate unimplemented input completion method for GSM
BenjaminVonBerg Sep 8, 2026
8d8b4a4
fix edsm
BenjaminVonBerg Sep 8, 2026
0aa095b
minor refactoring / cleanup and lots of docstrings
BenjaminVonBerg Sep 8, 2026
fe4c18e
fix testsuite
BenjaminVonBerg Sep 8, 2026
ca98629
Merge remote-tracking branch 'upstream/master' into gsm-v2
BenjaminVonBerg Sep 9, 2026
50b55d6
refactor: createPTA is now a method of DataHandler + minor stuff
BenjaminVonBerg Sep 9, 2026
92b8a7e
implement datahandlers for labeled sequences
BenjaminVonBerg Sep 9, 2026
d32e715
slimmer imports
BenjaminVonBerg Sep 9, 2026
2d05160
better docstring
BenjaminVonBerg Sep 9, 2026
ad72b9c
eliminate pta_preprocessing in favor for DH override
BenjaminVonBerg Sep 9, 2026
49b96cd
gsm v2 bughunt
BenjaminVonBerg Sep 10, 2026
466a183
GSM , the return of the bughunt
emuskardin Sep 10, 2026
8234c00
GSM , the return of the bughunt
emuskardin Sep 10, 2026
b744e37
Add tests for associated_data
emuskardin Sep 10, 2026
f432db7
drop dead code
BenjaminVonBerg Sep 11, 2026
8ead44d
make data handler limitations explicit
BenjaminVonBerg Sep 11, 2026
d8d49a0
fixed silent overwrite on nondeterminism for non-prefix-closed data
BenjaminVonBerg Sep 11, 2026
145f1f6
add additional flag for overriding default checks
BenjaminVonBerg Sep 11, 2026
d7e3698
added warnings for unknown outputs and input completeness in final au…
BenjaminVonBerg Sep 14, 2026
51b688a
remove testcase for unsupported feature
BenjaminVonBerg Sep 15, 2026
514d0af
fix broken test cases. removed empty test file
BenjaminVonBerg Sep 15, 2026
17d182c
Address documentation inconsistencies and bugs in GSM
emuskardin Sep 15, 2026
a1ee08b
consistent string value for input completeness with self loops
BenjaminVonBerg Sep 16, 2026
0c4783e
split early score calculation and score initialization + homogenized …
BenjaminVonBerg Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions Examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ def passive_vpa_learning_arithmetics():
def passive_vpa_learning_on_all_benchmark_models():
from aalpy.learning_algs import run_PAPNI
from aalpy.utils.BenchmarkVpaModels import vpa_L1, vpa_L12, vpa_for_odd_parentheses
from aalpy.utils import generate_input_output_data_from_vpa, convert_i_o_traces_for_RPNI
from aalpy.utils import generate_input_output_data_from_vpa

for gt in [vpa_L1(), vpa_L12(), vpa_for_odd_parentheses()]:
vpa_alphabet = gt.input_alphabet
Expand Down Expand Up @@ -1200,7 +1200,7 @@ def gsm_edsm():
from aalpy import load_automaton_from_file
from aalpy.utils.Sampling import get_io_traces, sample_with_length_limits
from aalpy.learning_algs.general_passive.GeneralizedStateMerging import run_GSM
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import ScoreCalculation
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import SimpleScoreCalculation
from aalpy.learning_algs.general_passive.GsmNode import GsmNode

automaton = load_automaton_from_file("DotModels/car_alarm.dot", "moore")
Expand All @@ -1212,7 +1212,7 @@ def EDSM_score(part: Dict[GsmNode, GsmNode]):
nr_merged = len(part)
return nr_merged - nr_partitions

score = ScoreCalculation(score_function=EDSM_score)
score = SimpleScoreCalculation(score_function=EDSM_score)
learned_model = run_GSM(traces, output_behavior="moore", transition_behavior="deterministic", score_calc=score)
learned_model.visualize()

Expand All @@ -1221,7 +1221,7 @@ def gsm_likelihood_ratio():
from typing import Dict
from scipy.stats import chi2
from aalpy.learning_algs.general_passive.GeneralizedStateMerging import run_GSM
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import ScoreFunction, differential_info, ScoreCalculation
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import ScoreFunction, differential_info, SimpleScoreCalculation
from aalpy.learning_algs.general_passive.GsmNode import GsmNode
from aalpy.utils.Sampling import get_io_traces, sample_with_length_limits
from aalpy import load_automaton_from_file
Expand All @@ -1246,55 +1246,58 @@ def score_fun(part: Dict[GsmNode, GsmNode]):

return score_fun

score = ScoreCalculation(score_function=likelihood_ratio_score())
score = SimpleScoreCalculation(score_function=likelihood_ratio_score())
learned_model = run_GSM(traces, output_behavior="moore", transition_behavior="stochastic", score_calc=score)
learned_model.visualize()


def example_Alergia_extension():
from typing import Any
from aalpy.learning_algs.general_passive.DataHandler import CountOnPTADataHandler
from aalpy.learning_algs.general_passive.GeneralizedStateMerging import run_GSM
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import hoeffding_compatibility, ScoreCalculation
from aalpy.learning_algs.general_passive.GsmNode import GsmNode
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import hoeffding_compatibility, SimpleFutureBasedCompatibility, SpecialScores
from aalpy.utils.Sampling import get_io_traces, sample_with_length_limits
from aalpy import load_automaton_from_file

automaton = load_automaton_from_file("DotModels/MDPs/faulty_car_alarm.dot", "mdp")
input_traces = sample_with_length_limits(automaton.get_input_alphabet(), 2000, 20, 30)
traces = get_io_traces(automaton, input_traces)

# NOTE THAT This example is equivalent to a call to a function run_Alergia_EDSM
# NOTE: a more general version of this is provided in aalpy.learning_algs.general_passive.ScoreFunctionsGSM
class ScoreIOAlergiaWithEDSM(SimpleFutureBasedCompatibility):
def __init__(self, eps: float):
self.compat = hoeffding_compatibility(eps)
SimpleFutureBasedCompatibility.__init__(self, compatibility_on_pta=True)
self.score = None

class IOAlergiaWithEDSM(ScoreCalculation):
def __init__(self, epsilon):
super().__init__()
self.ioa_compatibility = hoeffding_compatibility(epsilon)
self.evidence = 0

def reset(self):
self.evidence = 0
def early_score(self, red: GsmNode, blue: GsmNode) -> Any:
self.score = 0
verdict = super().early_score(red, blue)
if verdict is SpecialScores.ImmediateReject:
return verdict
return self.score

def local_compatibility(self, a: GsmNode, b: GsmNode):
self.evidence += 1
return self.ioa_compatibility(a, b)

def score_function(self, part):
return self.evidence
def local_compatibility(self, red: GsmNode, blue: GsmNode) -> float:
self.score += 1
return self.compat(red, blue)

epsilon = 0.05
scores = {
"IOA": ScoreCalculation(hoeffding_compatibility(epsilon)),
"IOA+EDSM": IOAlergiaWithEDSM(epsilon),
"IOA": SimpleFutureBasedCompatibility(local_compatibility=hoeffding_compatibility(epsilon, True), compatibility_on_pta=True),
"IOA+EDSM": ScoreIOAlergiaWithEDSM(epsilon),
}

for name, score in scores.items():
learned_model = run_GSM(traces, output_behavior="moore", transition_behavior="stochastic", score_calc=score,
compatibility_on_pta=True, compatibility_on_futures=True)
data_handler=CountOnPTADataHandler())
learned_model.visualize(name)


def gsm_IOAlergia_domain_knowldege():
from aalpy.learning_algs.general_passive.GeneralizedStateMerging import run_GSM
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import hoeffding_compatibility, ScoreCalculation
from aalpy.learning_algs.general_passive.ScoreFunctionsGSM import hoeffding_compatibility, SimpleFutureBasedCompatibility
from aalpy.learning_algs.general_passive.DataHandler import CountOnPTADataHandler
from aalpy.learning_algs.general_passive.GsmNode import GsmNode
from aalpy.utils.Sampling import get_io_traces, sample_with_length_limits
from aalpy import load_automaton_from_file
Expand All @@ -1320,12 +1323,12 @@ def ioa_compat_domain_knowledge(a: GsmNode, b: GsmNode):
return parity and ioa

scores = {
"IOA": ScoreCalculation(ioa_compat),
"IOA+DK": ScoreCalculation(ioa_compat_domain_knowledge),
"IOA": SimpleFutureBasedCompatibility(local_compatibility=ioa_compat, compatibility_on_pta=True),
"IOA+DK": SimpleFutureBasedCompatibility(local_compatibility=ioa_compat_domain_knowledge, compatibility_on_pta=True),
}
for name, score in scores.items():
learned_model = run_GSM(traces, output_behavior="moore", transition_behavior="stochastic", score_calc=score,
compatibility_on_pta=True, compatibility_on_futures=True)
data_handler=CountOnPTADataHandler())
learned_model.visualize(name)

def k_tails_example():
Expand All @@ -1336,16 +1339,13 @@ def k_tails_example():
input_alphabet_size=3,
output_alphabet_size=3)

# data is a list of sequences in this format [(i1, o1), (i2, o1), (i1, o3)]
data = generate_input_output_data_from_automata(model, num_sequences=2000,
min_seq_len=1, max_seq_len=12,
sequance_type='io_traces')

# k-trails works with prefix-closed input output traces, not labeled sequences like RPNI
# data is a list of sequences in this format [(i1, o1), (i2, o1), (i1, o3)]

# run k_tails with two different k's
k_trails_1 = run_k_tails(data, k=3, automaton_type='moore', print_info=True)

k_tails_1 = run_k_tails(data, k=3, automaton_type='moore', print_info=True)
k_tails_2 = run_k_tails(data, k=8, automaton_type='mealy', print_info=True)


Expand Down
2 changes: 1 addition & 1 deletion aalpy/learning_algs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
from .deterministic_passive.PAPNI import run_PAPNI
from .deterministic_passive.active_RPNI import run_active_RPNI
from .general_passive.GeneralizedStateMerging import run_GSM
from .general_passive.GsmAlgorithms import run_EDSM, run_Alergia_EDSM, run_k_tails
from .general_passive.GsmAlgorithms import run_EDSM, run_Alergia_GSM, run_Alergia_EDSM, run_k_tails
from .resetless.hW import run_hW
from .resetless.resetless_oracles import hWOracle, RandomhWOracle, RandomWphWOracle
63 changes: 63 additions & 0 deletions aalpy/learning_algs/general_passive/AssociatedData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import math
from abc import abstractmethod, ABC
from collections import defaultdict
from typing import Any


ProbabilityDict = dict[Any, dict[Any, float]]

class StochasticData(ABC):
"""
Interface class for data used with `transition_behavior` set to "stochastic".
"""
@abstractmethod
def get_probabilities(self) -> ProbabilityDict:
"""
Method for extracting transition probabilities when converting to automaton models.

:return ProbabilityDict: Nested dictionary of transition probabilities.
"""
pass

CountDict = dict[Any, dict[Any, int]]

def int_dict_increment(c_dict, out_sym, cnt):
c_dict[out_sym] = c_dict.get(out_sym, 0) + cnt

class CountData(StochasticData):
def __init__(self):
# TODO get rid of this indirection
self.transition_count: CountDict = defaultdict(dict)

def local_log_likelihood_contribution(self):
llc = 0
for in_sym, trans in self.transition_count.items():
total_count = 0
for out_sym, count in trans.items():
total_count += count
llc += count * math.log(count)
if total_count != 0:
llc -= total_count * math.log(total_count)
return llc

def count(self):
return sum(sum(trans.values()) for trans in self.transition_count.values())

def get_probabilities(self) -> ProbabilityDict:
ret = dict()
for in_sym, trans in self.transition_count.items():
total_count = sum(trans.values())
ret[in_sym] = {out_sym: count / total_count for out_sym, count in trans.items()}
return ret


ShadowPTA = dict[Any, dict[Any, 'GsmNode']]
class ShadowPTAData:
def __init__(self):
self.shadow_pta: ShadowPTA = defaultdict(dict)

class CountOnPTAData(ShadowPTAData, CountData):
def __init__(self):
ShadowPTAData.__init__(self)
CountData.__init__(self)
self.pta_count: CountDict = defaultdict(dict)
Loading
Loading