amachine.am_create API documentation

GitLab Repo

# amachine.am_create

View Source

      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: View Source

     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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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