amachine.am_create
1from collections import defaultdict 2import copy 3import random 4 5import numpy as np 6 7from .am_hmm import HMM 8from .am_causal_state import CausalState 9from .am_transition import Transition 10 11from .am_random import uniform_dist, exp_uniform_blend, resolve_rng 12 13from .am_vocabulary import Vocabulary 14 15def star_join( 16 exit_symbol : str, 17 enter_symbols : list[str], 18 machines : list[HMM], 19 mode_residency_factor : float ) -> HMM : 20 21 machine = HMM() 22 23 isomorphic_groups = defaultdict(list) 24 for i, m in enumerate( machines ) : 25 if m.isoclass is not None : 26 isomorphic_groups[ m.isoclass ].append( i ) 27 28 # since we are merging multuple machines which might have name collision 29 # we need to rename the states to ensure uniqueness 30 def rename_state( base_name : str, g : int ) : 31 return f"{g}/{base_name}" 32 33 def get_gid( idx : int, isoclass : int | None ) : 34 return idx if isoclass is None else isoclass 35 36 machine.set_alphabet( [ exit_symbol ] ) 37 38 for m in machines : 39 machine.extend_alphabet( alphabet=m.alphabet ) 40 41 # create a connector state and connector state class 42 43 connector_state = CausalState( 44 name=f"/c", 45 classes=set({"connector"}) 46 ) 47 48 # initial states before adding each machines states 49 machine.set_states( [ connector_state ] ) 50 machine.start_state = 0 51 52 # number of machines (groups of states) 53 n_groups = len( machines ) 54 55 # make sure we have enough symbols (otherwise connector can't be unifilar) 56 if n_groups > len(enter_symbols) : 57 raise Exception( 58 f"Too few enter symbols given number of machines" 59 ) 60 61 # for each given machine 62 for m_idx, m in enumerate( machines ) : 63 64 # default to the index of the machine in the list 65 m_gid = get_gid( m_idx, m.isoclass ) 66 67 # give the states from this machine a class name 68 m_classes = { 69 f"m_{m_idx}", 70 f"isoclass_{m.isoclass}" 71 } 72 73 added_states = [] 74 75 # create a state and extend our existing machine to include it 76 for s_idx, state in enumerate( m.states ) : 77 78 isomorphs=set() 79 if m.isoclass is not None and m.isoclass in isomorphic_groups : 80 81 for other_idx in isomorphic_groups[ m.isoclass ] : 82 83 if other_idx == m_idx : 84 continue 85 86 other_m = machines[ other_idx ] 87 isomorphs.add( 88 rename_state( 89 other_m.states[ s_idx ].name, 90 get_gid( other_idx, other_m.isoclass ) ) 91 ) 92 93 added_states.append( 94 CausalState( 95 name=rename_state(state.name, m_gid), 96 classes=( m_classes | state.classes ), 97 isomorphs=isomorphs 98 ) 99 ) 100 101 machine.extend_states( added_states ) 102 103 added_transitions = [] 104 105 # add all of the transitions from the machine 106 for tr in m.transitions : 107 108 # get the names of the states for the transition 109 origin_state_name = rename_state( m.states[ tr.origin_state_idx ].name, m_gid ) 110 target_state_name = rename_state( m.states[ tr.target_state_idx ].name, m_gid ) 111 112 # idx of the symbol remaped to this machines alphabet list 113 new_symbol_idx = machine.symbol_idx_map[ m.alphabet[ tr.symbol_idx ] ] 114 115 # create and add the new transition 116 added_transitions.append( Transition( 117 origin_state_idx=machine.state_idx_map[ origin_state_name ], 118 target_state_idx=machine.state_idx_map[ target_state_name ], 119 prob=tr.prob, 120 symbol_idx=new_symbol_idx 121 ) ) 122 123 machine.extend_transitions( added_transitions ) 124 125 # Add connector transitions, and adjust transition probabilities to sum to 1 126 127 # the name of the state that is the entry point to this group from the connector 128 m_entry_state_name = rename_state( m.states[ m.start_state ].name, m_gid ) 129 130 # get the index of the entry state for this machine 131 m_entry_state_idx = machine.state_idx_map[ m_entry_state_name ] 132 133 134 # Get the within group transitions from m's entry state 135 # ( the probabilities will need to be adjusted ) 136 transition_ids_from_m_entry = set() 137 for i, tr in enumerate( machine.transitions ) : 138 if tr.origin_state_idx == m_entry_state_idx : 139 transition_ids_from_m_entry.add( i ) 140 141 n_from_entry = len( transition_ids_from_m_entry ) 142 143 # Pr of staying in this group is distributed over the within group outgoing edges from the entry state 144 for i in transition_ids_from_m_entry : 145 146 machine.transitions[ i ] = Transition( 147 origin_state_idx=machine.transitions[ i ].origin_state_idx, 148 target_state_idx=machine.transitions[ i ].target_state_idx, 149 prob=mode_residency_factor / n_from_entry, 150 symbol_idx=machine.transitions[ i ].symbol_idx 151 ) 152 153 # from m's entry state back to connector 154 escape_pr = 1.0 - mode_residency_factor 155 156 machine.extend_transitions( transitions=[ 157 Transition( 158 origin_state_idx=m_entry_state_idx, 159 target_state_idx=machine.start_state, 160 prob=escape_pr, 161 symbol_idx=machine.symbol_idx_map[ exit_symbol ] 162 ) 163 ] ) 164 165 # from the connector to m's entry state 166 machine.extend_alphabet( alphabet=[ enter_symbols[ m_idx ] ] ) 167 168 machine.extend_transitions( transitions=[ 169 Transition( 170 origin_state_idx=machine.start_state, 171 target_state_idx=m_entry_state_idx, 172 prob=( 1.0 / n_groups ), 173 symbol_idx=machine.symbol_idx_map[ enter_symbols[ m_idx ] ] 174 ) 175 ] ) 176 177 return machine 178 179def star( 180 exit_symbol : str, 181 enter_symbols : list[str], 182 normal_symbols : list[str], 183 n_modes : int = 7, 184 n_isomorphic : int = 2, 185 randomness : float = 0.3, 186 connectedness : float = 0.5, 187 residency_factor : float = 0.5, 188 n_normal_symbols : int = 4, 189 t_states_per_machine : int = 17 ) -> HMM : 190 191 if len( normal_symbols ) < n_normal_symbols*n_isomorphic : 192 raise ValueError( "Must have at least n_normal_symbols*n_isomorphic normal symbols" ) 193 194 if len( enter_symbols ) < n_modes*n_isomorphic : 195 raise ValueError( "Must have at least n_modes*n_isomorphic enter symbols" ) 196 197 alphabet = [ f"{normal_symbols[i]}" for i in range( 0, n_normal_symbols ) ] 198 iso_alphabet = [ f"{normal_symbols[i]}" for i in range( n_normal_symbols, n_normal_symbols*2 ) ] 199 200 random_machines = [] 201 202 for i in range( n_modes ) : 203 204 m = random_machine( 205 n_states=t_states_per_machine, 206 symbols=alphabet, 207 randomness=randomness, 208 connectedness=connectedness ) 209 210 m.collapse_to_largest_strongly_connected_subgraph() 211 m_iso = isomorphic_to( m, alphabet=iso_alphabet ) 212 213 m.isoclass = f"{i}" 214 m_iso.isoclass = f"{i}" 215 216 for j, state in enumerate( m.states ) : 217 m.states[ j ].add_isomorph( m_iso.states[ j ].name ) 218 m_iso.states[ j ].add_isomorph( m.states[ j ].name ) 219 220 random_machines.append( m ) 221 random_machines.append( m_iso ) 222 223 mode_machine = star_join( 224 exit_symbol=exit_symbol, 225 enter_symbols=enter_symbols, 226 machines=random_machines, 227 mode_residency_factor=residency_factor 228 ) 229 230 return mode_machine 231 232 233def isomorphic_to( 234 m : HMM, 235 alphabet : list[str], 236 decorator : str = '@' ) -> HMM : 237 238 # make sure there are enough symbols 239 if len( alphabet ) < len( m.alphabet ) : 240 raise ValueError( "Not enough symbols in the alphabet" ) 241 242 # take the as much of them as needed 243 alphabet_used = alphabet[ 0 : len( m.alphabet ) ] 244 245 states = [ 246 CausalState( 247 name=f"{s.name}{decorator}", 248 classes=copy.deepcopy( s.classes ) 249 ) 250 for s in m.states 251 ] 252 253 return HMM( 254 states=states, 255 transitions=copy.deepcopy( m.transitions ), 256 start_state=0, 257 alphabet=alphabet_used 258 ) 259 260def random_machine( 261 n_states : int, 262 symbols : list[str], 263 connectedness : float, 264 randomness : float, 265 random_seed : int | None = None ) -> HMM : 266 267 if random_seed is not None: 268 py_rng = random.Random(random_seed) 269 np_rng = np.random.default_rng(random_seed) 270 else: 271 py_rng = random 272 np_rng = resolve_rng(None) 273 274 states=[ 275 CausalState( name=f"{i}" ) 276 for i in range( n_states ) 277 ] 278 279 n_symbols = len( symbols ) 280 transitions = [] 281 282 for state_idx, state in enumerate( states ) : 283 284 n_transitions = sum( py_rng.random() < connectedness for _ in range( n_symbols - 1 ) ) + 1 285 transition_to = py_rng.sample( range( n_states ), n_transitions ) 286 287 transition_probabilities = exp_uniform_blend( 288 n=n_transitions, 289 alpha=randomness, 290 np_rng=np_rng ) 291 292 transition_symbols_indices = py_rng.sample( range( n_symbols ), n_transitions ) 293 294 for i, p in enumerate( transition_probabilities ) : 295 transitions.append( 296 Transition( 297 origin_state_idx=state_idx, 298 target_state_idx=transition_to[ i ], 299 prob=p, 300 symbol_idx=transition_symbols_indices[ i ] 301 ) 302 ) 303 304 return HMM( 305 states=states, 306 transitions=transitions, 307 start_state=0, 308 alphabet=symbols.copy() 309 ) 310 311def full_isomorphic_rotation( 312 isoclass_name : str, 313 n_states : int, 314 n_base_symbols : int, 315 connectedness : float, 316 randomness : float, 317 mode_residency_factor : float, 318 random_seed : int | None = None ) -> HMM : 319 320 base_symbol_pool = Vocabulary.digits() + Vocabulary.geometric() 321 enter_symbol_pool = Vocabulary.greek_lower() + Vocabulary.greek_upper() 322 exit_symbol = '*' 323 324 max_n_symbols = min( len(base_symbol_pool), len(enter_symbol_pool) ) 325 326 if n_base_symbols > max_n_symbols : 327 raise ValueError( f"Only up to {len(base_symbol_pool)} base symbols supported" ) 328 329 alphabet = base_symbol_pool[ 0:n_base_symbols ] 330 enter_symbols = enter_symbol_pool[ 0:n_base_symbols ] 331 332 m = random_machine( 333 n_states=n_states, 334 symbols=alphabet, 335 randomness=randomness, 336 connectedness=connectedness ) 337 338 m.collapse_to_largest_strongly_connected_subgraph() 339 m.minimize() 340 341 machines = [] 342 for i in range( n_base_symbols ) : 343 344 alphabet = alphabet[ -1: ] + alphabet[ 0 : n_base_symbols - 1 ] 345 im = isomorphic_to( m, alphabet=alphabet, decorator=enter_symbols[ i ] ) 346 im.isoclass = isoclass_name 347 machines.append( im ) 348 349 for im in machines : 350 for state in im.states : 351 352 # get the undecorated name 353 undecorated = state.name[ 0:-1 ] 354 my_decoration = state.name[-1] 355 356 # capture decorated names of all isomorphic states 357 for a in enter_symbols : 358 359 # self not storing self as isomorph 360 if a == my_decoration : 361 continue 362 363 decorated = undecorated + a 364 state.add_isomorph( decorated ) 365 366 final_machine = star_join( 367 exit_symbol=exit_symbol, 368 enter_symbols=enter_symbols, 369 machines=machines, 370 mode_residency_factor=mode_residency_factor 371 ) 372 373 return final_machine 374 375def unique_isomorphic( 376 isoclass_name : str, 377 n_machines : int, 378 n_states : int, 379 n_base_symbols : int, 380 connectedness : float, 381 randomness : float, 382 mode_residency_factor : float, 383 random_seed : int | None = None ) -> HMM : 384 385 base_symbol_pool = Vocabulary.digits() + Vocabulary.geometric() 386 enter_symbol_pool = Vocabulary.greek_lower() + Vocabulary.greek_upper() 387 exit_symbol = '*' 388 389 max_n_symbols = min( len(base_symbol_pool), len(enter_symbol_pool) ) 390 391 if n_base_symbols*n_machines > max_n_symbols : 392 raise ValueError( f"Only up to {len(base_symbol_pool)} base symbols supported" ) 393 394 alphabets = [ 395 base_symbol_pool[ start:start + n_base_symbols ] 396 for start in range( 0, n_base_symbols*n_machines, n_base_symbols ) 397 ] 398 enter_symbols = enter_symbol_pool[ 0:n_machines ] 399 400 m = random_machine( 401 n_states=n_states, 402 symbols=alphabets[0], 403 randomness=randomness, 404 connectedness=connectedness ) 405 406 m.isoclass = isoclass_name 407 408 m.collapse_to_largest_strongly_connected_subgraph() 409 m.minimize() 410 411 machines = [ m ] 412 for i in range( 1, n_machines ) : 413 414 alphabet = alphabets[ i ] 415 im = isomorphic_to( m, alphabet=alphabet, decorator=enter_symbols[ i ] ) 416 im.isoclass = isoclass_name 417 machines.append( im ) 418 419 for im in machines : 420 for state in im.states : 421 422 # get the undecorated name 423 undecorated = state.name[ 0:-1 ] 424 my_decoration = state.name[-1] 425 426 # capture decorated names of all isomorphic states 427 for a in enter_symbols : 428 429 # self not storing self as isomorph 430 if a == my_decoration : 431 continue 432 433 decorated = undecorated + a 434 state.add_isomorph( decorated ) 435 436 final_machine = star_join( 437 exit_symbol=exit_symbol, 438 enter_symbols=enter_symbols, 439 machines=machines, 440 mode_residency_factor=mode_residency_factor 441 ) 442 443 return final_machine
def
star_join( exit_symbol: str, enter_symbols: list[str], machines: list[amachine.am_hmm.HMM], mode_residency_factor: float) -> amachine.am_hmm.HMM:
16def star_join( 17 exit_symbol : str, 18 enter_symbols : list[str], 19 machines : list[HMM], 20 mode_residency_factor : float ) -> HMM : 21 22 machine = HMM() 23 24 isomorphic_groups = defaultdict(list) 25 for i, m in enumerate( machines ) : 26 if m.isoclass is not None : 27 isomorphic_groups[ m.isoclass ].append( i ) 28 29 # since we are merging multuple machines which might have name collision 30 # we need to rename the states to ensure uniqueness 31 def rename_state( base_name : str, g : int ) : 32 return f"{g}/{base_name}" 33 34 def get_gid( idx : int, isoclass : int | None ) : 35 return idx if isoclass is None else isoclass 36 37 machine.set_alphabet( [ exit_symbol ] ) 38 39 for m in machines : 40 machine.extend_alphabet( alphabet=m.alphabet ) 41 42 # create a connector state and connector state class 43 44 connector_state = CausalState( 45 name=f"/c", 46 classes=set({"connector"}) 47 ) 48 49 # initial states before adding each machines states 50 machine.set_states( [ connector_state ] ) 51 machine.start_state = 0 52 53 # number of machines (groups of states) 54 n_groups = len( machines ) 55 56 # make sure we have enough symbols (otherwise connector can't be unifilar) 57 if n_groups > len(enter_symbols) : 58 raise Exception( 59 f"Too few enter symbols given number of machines" 60 ) 61 62 # for each given machine 63 for m_idx, m in enumerate( machines ) : 64 65 # default to the index of the machine in the list 66 m_gid = get_gid( m_idx, m.isoclass ) 67 68 # give the states from this machine a class name 69 m_classes = { 70 f"m_{m_idx}", 71 f"isoclass_{m.isoclass}" 72 } 73 74 added_states = [] 75 76 # create a state and extend our existing machine to include it 77 for s_idx, state in enumerate( m.states ) : 78 79 isomorphs=set() 80 if m.isoclass is not None and m.isoclass in isomorphic_groups : 81 82 for other_idx in isomorphic_groups[ m.isoclass ] : 83 84 if other_idx == m_idx : 85 continue 86 87 other_m = machines[ other_idx ] 88 isomorphs.add( 89 rename_state( 90 other_m.states[ s_idx ].name, 91 get_gid( other_idx, other_m.isoclass ) ) 92 ) 93 94 added_states.append( 95 CausalState( 96 name=rename_state(state.name, m_gid), 97 classes=( m_classes | state.classes ), 98 isomorphs=isomorphs 99 ) 100 ) 101 102 machine.extend_states( added_states ) 103 104 added_transitions = [] 105 106 # add all of the transitions from the machine 107 for tr in m.transitions : 108 109 # get the names of the states for the transition 110 origin_state_name = rename_state( m.states[ tr.origin_state_idx ].name, m_gid ) 111 target_state_name = rename_state( m.states[ tr.target_state_idx ].name, m_gid ) 112 113 # idx of the symbol remaped to this machines alphabet list 114 new_symbol_idx = machine.symbol_idx_map[ m.alphabet[ tr.symbol_idx ] ] 115 116 # create and add the new transition 117 added_transitions.append( Transition( 118 origin_state_idx=machine.state_idx_map[ origin_state_name ], 119 target_state_idx=machine.state_idx_map[ target_state_name ], 120 prob=tr.prob, 121 symbol_idx=new_symbol_idx 122 ) ) 123 124 machine.extend_transitions( added_transitions ) 125 126 # Add connector transitions, and adjust transition probabilities to sum to 1 127 128 # the name of the state that is the entry point to this group from the connector 129 m_entry_state_name = rename_state( m.states[ m.start_state ].name, m_gid ) 130 131 # get the index of the entry state for this machine 132 m_entry_state_idx = machine.state_idx_map[ m_entry_state_name ] 133 134 135 # Get the within group transitions from m's entry state 136 # ( the probabilities will need to be adjusted ) 137 transition_ids_from_m_entry = set() 138 for i, tr in enumerate( machine.transitions ) : 139 if tr.origin_state_idx == m_entry_state_idx : 140 transition_ids_from_m_entry.add( i ) 141 142 n_from_entry = len( transition_ids_from_m_entry ) 143 144 # Pr of staying in this group is distributed over the within group outgoing edges from the entry state 145 for i in transition_ids_from_m_entry : 146 147 machine.transitions[ i ] = Transition( 148 origin_state_idx=machine.transitions[ i ].origin_state_idx, 149 target_state_idx=machine.transitions[ i ].target_state_idx, 150 prob=mode_residency_factor / n_from_entry, 151 symbol_idx=machine.transitions[ i ].symbol_idx 152 ) 153 154 # from m's entry state back to connector 155 escape_pr = 1.0 - mode_residency_factor 156 157 machine.extend_transitions( transitions=[ 158 Transition( 159 origin_state_idx=m_entry_state_idx, 160 target_state_idx=machine.start_state, 161 prob=escape_pr, 162 symbol_idx=machine.symbol_idx_map[ exit_symbol ] 163 ) 164 ] ) 165 166 # from the connector to m's entry state 167 machine.extend_alphabet( alphabet=[ enter_symbols[ m_idx ] ] ) 168 169 machine.extend_transitions( transitions=[ 170 Transition( 171 origin_state_idx=machine.start_state, 172 target_state_idx=m_entry_state_idx, 173 prob=( 1.0 / n_groups ), 174 symbol_idx=machine.symbol_idx_map[ enter_symbols[ m_idx ] ] 175 ) 176 ] ) 177 178 return machine
def
star( exit_symbol: str, enter_symbols: list[str], normal_symbols: list[str], n_modes: int = 7, n_isomorphic: int = 2, randomness: float = 0.3, connectedness: float = 0.5, residency_factor: float = 0.5, n_normal_symbols: int = 4, t_states_per_machine: int = 17) -> amachine.am_hmm.HMM:
180def star( 181 exit_symbol : str, 182 enter_symbols : list[str], 183 normal_symbols : list[str], 184 n_modes : int = 7, 185 n_isomorphic : int = 2, 186 randomness : float = 0.3, 187 connectedness : float = 0.5, 188 residency_factor : float = 0.5, 189 n_normal_symbols : int = 4, 190 t_states_per_machine : int = 17 ) -> HMM : 191 192 if len( normal_symbols ) < n_normal_symbols*n_isomorphic : 193 raise ValueError( "Must have at least n_normal_symbols*n_isomorphic normal symbols" ) 194 195 if len( enter_symbols ) < n_modes*n_isomorphic : 196 raise ValueError( "Must have at least n_modes*n_isomorphic enter symbols" ) 197 198 alphabet = [ f"{normal_symbols[i]}" for i in range( 0, n_normal_symbols ) ] 199 iso_alphabet = [ f"{normal_symbols[i]}" for i in range( n_normal_symbols, n_normal_symbols*2 ) ] 200 201 random_machines = [] 202 203 for i in range( n_modes ) : 204 205 m = random_machine( 206 n_states=t_states_per_machine, 207 symbols=alphabet, 208 randomness=randomness, 209 connectedness=connectedness ) 210 211 m.collapse_to_largest_strongly_connected_subgraph() 212 m_iso = isomorphic_to( m, alphabet=iso_alphabet ) 213 214 m.isoclass = f"{i}" 215 m_iso.isoclass = f"{i}" 216 217 for j, state in enumerate( m.states ) : 218 m.states[ j ].add_isomorph( m_iso.states[ j ].name ) 219 m_iso.states[ j ].add_isomorph( m.states[ j ].name ) 220 221 random_machines.append( m ) 222 random_machines.append( m_iso ) 223 224 mode_machine = star_join( 225 exit_symbol=exit_symbol, 226 enter_symbols=enter_symbols, 227 machines=random_machines, 228 mode_residency_factor=residency_factor 229 ) 230 231 return mode_machine
def
isomorphic_to( m: amachine.am_hmm.HMM, alphabet: list[str], decorator: str = '@') -> amachine.am_hmm.HMM:
234def isomorphic_to( 235 m : HMM, 236 alphabet : list[str], 237 decorator : str = '@' ) -> HMM : 238 239 # make sure there are enough symbols 240 if len( alphabet ) < len( m.alphabet ) : 241 raise ValueError( "Not enough symbols in the alphabet" ) 242 243 # take the as much of them as needed 244 alphabet_used = alphabet[ 0 : len( m.alphabet ) ] 245 246 states = [ 247 CausalState( 248 name=f"{s.name}{decorator}", 249 classes=copy.deepcopy( s.classes ) 250 ) 251 for s in m.states 252 ] 253 254 return HMM( 255 states=states, 256 transitions=copy.deepcopy( m.transitions ), 257 start_state=0, 258 alphabet=alphabet_used 259 )
def
random_machine( n_states: int, symbols: list[str], connectedness: float, randomness: float, random_seed: int | None = None) -> amachine.am_hmm.HMM:
261def random_machine( 262 n_states : int, 263 symbols : list[str], 264 connectedness : float, 265 randomness : float, 266 random_seed : int | None = None ) -> HMM : 267 268 if random_seed is not None: 269 py_rng = random.Random(random_seed) 270 np_rng = np.random.default_rng(random_seed) 271 else: 272 py_rng = random 273 np_rng = resolve_rng(None) 274 275 states=[ 276 CausalState( name=f"{i}" ) 277 for i in range( n_states ) 278 ] 279 280 n_symbols = len( symbols ) 281 transitions = [] 282 283 for state_idx, state in enumerate( states ) : 284 285 n_transitions = sum( py_rng.random() < connectedness for _ in range( n_symbols - 1 ) ) + 1 286 transition_to = py_rng.sample( range( n_states ), n_transitions ) 287 288 transition_probabilities = exp_uniform_blend( 289 n=n_transitions, 290 alpha=randomness, 291 np_rng=np_rng ) 292 293 transition_symbols_indices = py_rng.sample( range( n_symbols ), n_transitions ) 294 295 for i, p in enumerate( transition_probabilities ) : 296 transitions.append( 297 Transition( 298 origin_state_idx=state_idx, 299 target_state_idx=transition_to[ i ], 300 prob=p, 301 symbol_idx=transition_symbols_indices[ i ] 302 ) 303 ) 304 305 return HMM( 306 states=states, 307 transitions=transitions, 308 start_state=0, 309 alphabet=symbols.copy() 310 )
def
full_isomorphic_rotation( isoclass_name: str, n_states: int, n_base_symbols: int, connectedness: float, randomness: float, mode_residency_factor: float, random_seed: int | None = None) -> amachine.am_hmm.HMM:
312def full_isomorphic_rotation( 313 isoclass_name : str, 314 n_states : int, 315 n_base_symbols : int, 316 connectedness : float, 317 randomness : float, 318 mode_residency_factor : float, 319 random_seed : int | None = None ) -> HMM : 320 321 base_symbol_pool = Vocabulary.digits() + Vocabulary.geometric() 322 enter_symbol_pool = Vocabulary.greek_lower() + Vocabulary.greek_upper() 323 exit_symbol = '*' 324 325 max_n_symbols = min( len(base_symbol_pool), len(enter_symbol_pool) ) 326 327 if n_base_symbols > max_n_symbols : 328 raise ValueError( f"Only up to {len(base_symbol_pool)} base symbols supported" ) 329 330 alphabet = base_symbol_pool[ 0:n_base_symbols ] 331 enter_symbols = enter_symbol_pool[ 0:n_base_symbols ] 332 333 m = random_machine( 334 n_states=n_states, 335 symbols=alphabet, 336 randomness=randomness, 337 connectedness=connectedness ) 338 339 m.collapse_to_largest_strongly_connected_subgraph() 340 m.minimize() 341 342 machines = [] 343 for i in range( n_base_symbols ) : 344 345 alphabet = alphabet[ -1: ] + alphabet[ 0 : n_base_symbols - 1 ] 346 im = isomorphic_to( m, alphabet=alphabet, decorator=enter_symbols[ i ] ) 347 im.isoclass = isoclass_name 348 machines.append( im ) 349 350 for im in machines : 351 for state in im.states : 352 353 # get the undecorated name 354 undecorated = state.name[ 0:-1 ] 355 my_decoration = state.name[-1] 356 357 # capture decorated names of all isomorphic states 358 for a in enter_symbols : 359 360 # self not storing self as isomorph 361 if a == my_decoration : 362 continue 363 364 decorated = undecorated + a 365 state.add_isomorph( decorated ) 366 367 final_machine = star_join( 368 exit_symbol=exit_symbol, 369 enter_symbols=enter_symbols, 370 machines=machines, 371 mode_residency_factor=mode_residency_factor 372 ) 373 374 return final_machine
def
unique_isomorphic( isoclass_name: str, n_machines: int, n_states: int, n_base_symbols: int, connectedness: float, randomness: float, mode_residency_factor: float, random_seed: int | None = None) -> amachine.am_hmm.HMM:
376def unique_isomorphic( 377 isoclass_name : str, 378 n_machines : int, 379 n_states : int, 380 n_base_symbols : int, 381 connectedness : float, 382 randomness : float, 383 mode_residency_factor : float, 384 random_seed : int | None = None ) -> HMM : 385 386 base_symbol_pool = Vocabulary.digits() + Vocabulary.geometric() 387 enter_symbol_pool = Vocabulary.greek_lower() + Vocabulary.greek_upper() 388 exit_symbol = '*' 389 390 max_n_symbols = min( len(base_symbol_pool), len(enter_symbol_pool) ) 391 392 if n_base_symbols*n_machines > max_n_symbols : 393 raise ValueError( f"Only up to {len(base_symbol_pool)} base symbols supported" ) 394 395 alphabets = [ 396 base_symbol_pool[ start:start + n_base_symbols ] 397 for start in range( 0, n_base_symbols*n_machines, n_base_symbols ) 398 ] 399 enter_symbols = enter_symbol_pool[ 0:n_machines ] 400 401 m = random_machine( 402 n_states=n_states, 403 symbols=alphabets[0], 404 randomness=randomness, 405 connectedness=connectedness ) 406 407 m.isoclass = isoclass_name 408 409 m.collapse_to_largest_strongly_connected_subgraph() 410 m.minimize() 411 412 machines = [ m ] 413 for i in range( 1, n_machines ) : 414 415 alphabet = alphabets[ i ] 416 im = isomorphic_to( m, alphabet=alphabet, decorator=enter_symbols[ i ] ) 417 im.isoclass = isoclass_name 418 machines.append( im ) 419 420 for im in machines : 421 for state in im.states : 422 423 # get the undecorated name 424 undecorated = state.name[ 0:-1 ] 425 my_decoration = state.name[-1] 426 427 # capture decorated names of all isomorphic states 428 for a in enter_symbols : 429 430 # self not storing self as isomorph 431 if a == my_decoration : 432 continue 433 434 decorated = undecorated + a 435 state.add_isomorph( decorated ) 436 437 final_machine = star_join( 438 exit_symbol=exit_symbol, 439 enter_symbols=enter_symbols, 440 machines=machines, 441 mode_residency_factor=mode_residency_factor 442 ) 443 444 return final_machine