amachine.am_create.am_random_structured
1from collections import defaultdict, Counter, deque 2import warnings 3 4import numpy as np 5import math 6import random 7from numba import njit 8 9from ..am_hmm import HMM 10from ..am_structured_symbol_set import StructuredSymbolSet 11from ..am_optimize.am_optimize_symbol_permutation_ils import( 12 optimize_symbol_permutation_ils 13) 14from ..am_optimize.am_optimize_symbol_permutation_cpsat import( 15 optimize_symbol_permutation_cpsat 16) 17 18from .am_random_machine import random_machine 19 20def get_score( 21 m, 22 symbol_set, 23 frequency_bias ) : 24 25 n_symbols = len(symbol_set.symbols) 26 27 original_symbols = [t.symbol_idx for t in m.transitions] 28 symbol_counts = Counter(original_symbols) 29 present_symbols = sorted(list(symbol_counts.keys())) 30 31 state_trs_in = defaultdict(list) 32 state_trs_out = defaultdict(list) 33 34 for i, tr in enumerate(m.transitions): 35 state_trs_in[tr.target_state_idx].append((i, tr)) 36 state_trs_out[tr.origin_state_idx].append((i, tr)) 37 38 pi = m.get_stationary_distribution() 39 tr_pair_probs = {} 40 41 for state_idx in range(len(m.states)): 42 for i_in, t_in in state_trs_in[state_idx]: 43 in_prob = pi[t_in.origin_state_idx] * t_in.prob 44 for i_out, t_out in state_trs_out[state_idx]: 45 tr_pair_probs[(i_in, i_out)] = in_prob * t_out.prob 46 47 n_pairs = len(tr_pair_probs) 48 uniform_w = 1.0 / n_pairs if n_pairs > 0 else 0.0 49 50 pair_weights = { 51 pair: (1.0 - frequency_bias) * uniform_w + frequency_bias * p 52 for pair, p in tr_pair_probs.items() 53 } 54 55 original_symbols = [ t.symbol_idx for t in m.transitions ] 56 57 rw = 0 58 for ( i_in, i_out ), _ in tr_pair_probs.items() : 59 60 orig_s_in = original_symbols[ i_in ] 61 orig_s_out = original_symbols[ i_out ] 62 63 w = pair_weights[ ( i_in, i_out ) ] 64 65 rw += w*symbol_set.symbol_cohesion[ orig_s_in, orig_s_out ] 66 67 return rw 68 69def random_structured( 70 n_states: int, 71 symbol_set: StructuredSymbolSet, 72 connectedness: float, 73 randomness: float, 74 frequency_bias: float = 0.8, 75 ensure_strongly_connected: bool = True, 76 ensure_minimal: bool = True, 77 np_rng : np.random.Generator | None = None ) -> HMM: 78 79 n_symbols = len(symbol_set.symbols) 80 81 # passing proportions will make random_machine attempt to 82 # assign symbols to edges as close as possible to those proportions 83 # instead of just random sampling symbols unformly 84 symbol_proportions = [ 1.0/n_symbols ]*n_symbols 85 86 m = random_machine( 87 n_states=n_states, 88 symbols=symbol_set.symbols, 89 connectedness=connectedness, 90 randomness=randomness, 91 symbol_proportions=symbol_proportions, 92 stratification_categories=symbol_set.symbol_categories, 93 ensure_strongly_connected=ensure_strongly_connected, 94 ensure_minimal=True, 95 np_rng=np_rng 96 ) 97 98 if not m.is_unifilar() : 99 raise ValueError( "random_machine returned a non-unifilar machine." ) 100 101 initial_symbols = [ tr.symbol_idx for tr in m.transitions ] 102 E = len( initial_symbols ) 103 symbol_counts = Counter( initial_symbols ).values() 104 numerator = math.factorial( E ) 105 denominator = math.prod( math.factorial( c ) for c in symbol_counts ) 106 estimated_search_space_size = numerator // denominator 107 108 # Note: CP-SAT is about on par with efficiently implemented brute force for this problem 109 # and ILS tends to find optimal soltion anyways when the search space is this small 110 # probably either always use ILS, or consider implementing pure brute force for small cases 111 # would be better, however there is a chance that a better cp-sat formulation chould change the 112 # calculus. 113 114 if estimated_search_space_size < 479_001_600 : 115 symbol_assignment = optimize_symbol_permutation_cpsat( 116 m=m, 117 symbol_set=symbol_set, 118 frequency_bias=frequency_bias, 119 n_search_workers=3, 120 max_search_time=60, 121 np_rng=np_rng 122 ) 123 else : 124 symbol_assignment = optimize_symbol_permutation_ils( 125 m=m, 126 symbol_set=symbol_set, 127 frequency_bias=frequency_bias, 128 ils_iterations=3_000, 129 kick_strength=4, 130 np_rng=np_rng 131 ) 132 133 new_transitions = [] 134 for i, s_idx in enumerate( symbol_assignment ): 135 new_transitions.append( 136 m.transitions[ i ].modified_deep_copy( symbol_idx=s_idx ) 137 ) 138 139 m.set_transitions( new_transitions ) 140 141 if ensure_minimal : 142 m.minimize() 143 144 if not m.is_row_stochastic() : 145 raise Exception( "Random structured machine is not row stochastic" ) 146 147 if not m.is_unifilar() : 148 raise Exception( "Random structured machine is not unifilar" ) 149 150 print( f"calculated score: {get_score( m, symbol_set, frequency_bias )}" ) 151 152 return m
def
get_score(m, symbol_set, frequency_bias):
21def get_score( 22 m, 23 symbol_set, 24 frequency_bias ) : 25 26 n_symbols = len(symbol_set.symbols) 27 28 original_symbols = [t.symbol_idx for t in m.transitions] 29 symbol_counts = Counter(original_symbols) 30 present_symbols = sorted(list(symbol_counts.keys())) 31 32 state_trs_in = defaultdict(list) 33 state_trs_out = defaultdict(list) 34 35 for i, tr in enumerate(m.transitions): 36 state_trs_in[tr.target_state_idx].append((i, tr)) 37 state_trs_out[tr.origin_state_idx].append((i, tr)) 38 39 pi = m.get_stationary_distribution() 40 tr_pair_probs = {} 41 42 for state_idx in range(len(m.states)): 43 for i_in, t_in in state_trs_in[state_idx]: 44 in_prob = pi[t_in.origin_state_idx] * t_in.prob 45 for i_out, t_out in state_trs_out[state_idx]: 46 tr_pair_probs[(i_in, i_out)] = in_prob * t_out.prob 47 48 n_pairs = len(tr_pair_probs) 49 uniform_w = 1.0 / n_pairs if n_pairs > 0 else 0.0 50 51 pair_weights = { 52 pair: (1.0 - frequency_bias) * uniform_w + frequency_bias * p 53 for pair, p in tr_pair_probs.items() 54 } 55 56 original_symbols = [ t.symbol_idx for t in m.transitions ] 57 58 rw = 0 59 for ( i_in, i_out ), _ in tr_pair_probs.items() : 60 61 orig_s_in = original_symbols[ i_in ] 62 orig_s_out = original_symbols[ i_out ] 63 64 w = pair_weights[ ( i_in, i_out ) ] 65 66 rw += w*symbol_set.symbol_cohesion[ orig_s_in, orig_s_out ] 67 68 return rw
def
random_structured( n_states: int, symbol_set: amachine.am_structured_symbol_set.StructuredSymbolSet, connectedness: float, randomness: float, frequency_bias: float = 0.8, ensure_strongly_connected: bool = True, ensure_minimal: bool = True, np_rng: numpy.random._generator.Generator | None = None) -> amachine.am_hmm.HMM:
70def random_structured( 71 n_states: int, 72 symbol_set: StructuredSymbolSet, 73 connectedness: float, 74 randomness: float, 75 frequency_bias: float = 0.8, 76 ensure_strongly_connected: bool = True, 77 ensure_minimal: bool = True, 78 np_rng : np.random.Generator | None = None ) -> HMM: 79 80 n_symbols = len(symbol_set.symbols) 81 82 # passing proportions will make random_machine attempt to 83 # assign symbols to edges as close as possible to those proportions 84 # instead of just random sampling symbols unformly 85 symbol_proportions = [ 1.0/n_symbols ]*n_symbols 86 87 m = random_machine( 88 n_states=n_states, 89 symbols=symbol_set.symbols, 90 connectedness=connectedness, 91 randomness=randomness, 92 symbol_proportions=symbol_proportions, 93 stratification_categories=symbol_set.symbol_categories, 94 ensure_strongly_connected=ensure_strongly_connected, 95 ensure_minimal=True, 96 np_rng=np_rng 97 ) 98 99 if not m.is_unifilar() : 100 raise ValueError( "random_machine returned a non-unifilar machine." ) 101 102 initial_symbols = [ tr.symbol_idx for tr in m.transitions ] 103 E = len( initial_symbols ) 104 symbol_counts = Counter( initial_symbols ).values() 105 numerator = math.factorial( E ) 106 denominator = math.prod( math.factorial( c ) for c in symbol_counts ) 107 estimated_search_space_size = numerator // denominator 108 109 # Note: CP-SAT is about on par with efficiently implemented brute force for this problem 110 # and ILS tends to find optimal soltion anyways when the search space is this small 111 # probably either always use ILS, or consider implementing pure brute force for small cases 112 # would be better, however there is a chance that a better cp-sat formulation chould change the 113 # calculus. 114 115 if estimated_search_space_size < 479_001_600 : 116 symbol_assignment = optimize_symbol_permutation_cpsat( 117 m=m, 118 symbol_set=symbol_set, 119 frequency_bias=frequency_bias, 120 n_search_workers=3, 121 max_search_time=60, 122 np_rng=np_rng 123 ) 124 else : 125 symbol_assignment = optimize_symbol_permutation_ils( 126 m=m, 127 symbol_set=symbol_set, 128 frequency_bias=frequency_bias, 129 ils_iterations=3_000, 130 kick_strength=4, 131 np_rng=np_rng 132 ) 133 134 new_transitions = [] 135 for i, s_idx in enumerate( symbol_assignment ): 136 new_transitions.append( 137 m.transitions[ i ].modified_deep_copy( symbol_idx=s_idx ) 138 ) 139 140 m.set_transitions( new_transitions ) 141 142 if ensure_minimal : 143 m.minimize() 144 145 if not m.is_row_stochastic() : 146 raise Exception( "Random structured machine is not row stochastic" ) 147 148 if not m.is_unifilar() : 149 raise Exception( "Random structured machine is not unifilar" ) 150 151 print( f"calculated score: {get_score( m, symbol_set, frequency_bias )}" ) 152 153 return m