GitLab Repo

amachine.am_create.am_star_join

  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 exp_uniform_blend, resolve_rng
 12from ..am_vocabulary import Vocabulary
 13
 14def star_join(
 15    exit_symbol : str, 
 16    enter_symbols : list[str],
 17    machines : list[HMM],
 18    mode_residency_factor : float ) -> HMM :
 19
 20    machine = HMM()
 21
 22    isomorphic_groups = defaultdict(list)
 23    for i, m in enumerate( machines ) :
 24        if m.isoclass is not None :
 25            isomorphic_groups[ m.isoclass ].append( i )
 26
 27    # since we are merging multuple machines which might have name collision
 28    # we need to rename the states to ensure uniqueness
 29    def rename_state( base_name : str, g : int ) :
 30        return f"{g}/{base_name}"
 31
 32    def get_gid( idx : int, isoclass : int | None ) :
 33        return idx if isoclass is None else isoclass
 34
 35    machine.set_alphabet( [ exit_symbol ] )
 36
 37    for m in machines :
 38        machine.extend_alphabet( alphabet=m.alphabet )
 39
 40    # create a connector state and connector state class
 41    connector_state = CausalState(
 42        name=f"/c", 
 43        classes=set({"connector"}) 
 44    )
 45    
 46    # initial states before adding each machines states
 47    machine.set_states( [ connector_state ] )
 48    machine.start_state = 0
 49
 50    # number of machines (groups of states)
 51    n_groups = len( machines )
 52
 53    # make sure we have enough symbols (otherwise connector can't be unifilar)
 54    if n_groups > len(enter_symbols) :
 55        raise Exception(
 56            f"Too few enter symbols given number of machines"
 57        )
 58
 59    # for each given machine
 60    for m_idx, m in enumerate( machines ) : 
 61
 62        # default to the index of the machine in the list
 63        m_gid = get_gid( m_idx, m.isoclass )
 64
 65        # give the states from this machine a class name
 66        m_classes = { 
 67            f"m_{m_idx}", 
 68            f"isoclass_{m.isoclass}" 
 69        }
 70
 71        added_states = []
 72        
 73        # create a state and extend our existing machine to include it
 74        for s_idx, state in enumerate( m.states ) :
 75
 76            isomorphs=set()
 77            if m.isoclass is not None and m.isoclass in isomorphic_groups :
 78
 79                for other_idx in isomorphic_groups[ m.isoclass ] :
 80                    
 81                    if other_idx == m_idx : 
 82                        continue
 83                    
 84                    other_m = machines[ other_idx ]
 85                    isomorphs.add(  
 86                        rename_state( 
 87                            other_m.states[ s_idx ].name, 
 88                            get_gid( other_idx, other_m.isoclass ) )
 89                    )
 90
 91            added_states.append( 
 92                CausalState( 
 93                    name=rename_state(state.name, m_gid),
 94                    classes=( m_classes | state.classes ),
 95                    isomorphs=isomorphs
 96                ) 
 97            )
 98
 99        machine.extend_states( added_states )
100
101        added_transitions = []
102
103        # add all of the transitions from the machine
104        for tr in m.transitions :
105
106            # get the names of the states for the transition
107            origin_state_name = rename_state( m.states[ tr.origin_state_idx ].name, m_gid )
108            target_state_name = rename_state( m.states[ tr.target_state_idx ].name, m_gid )
109
110            # idx of the symbol remaped to this machines alphabet list
111            new_symbol_idx = machine.symbol_idx_map[ m.alphabet[ tr.symbol_idx ] ]
112
113            # create and add the new transition
114            added_transitions.append( Transition(
115                origin_state_idx=machine.state_idx_map[ origin_state_name ],
116                target_state_idx=machine.state_idx_map[ target_state_name ],
117                prob=tr.prob,
118                symbol_idx=new_symbol_idx,
119                pq=None,
120                cross_component=False
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                pq=None,
152                cross_component=False
153            )
154
155        # from m's entry state back to connector
156        escape_pr = 1.0 - mode_residency_factor
157
158        machine.extend_transitions( transitions=[
159            Transition(
160                origin_state_idx=m_entry_state_idx,
161                target_state_idx=machine.start_state,
162                prob=escape_pr,
163                symbol_idx=machine.symbol_idx_map[ exit_symbol ],
164                pq=None,
165                cross_component=False
166            )
167        ] )
168
169        # from the connector to m's entry state
170        machine.extend_alphabet( alphabet=[ enter_symbols[ m_idx ] ] )
171        
172        machine.extend_transitions( transitions=[
173            Transition(
174                origin_state_idx=machine.start_state,
175                target_state_idx=m_entry_state_idx,
176                prob=( 1.0 / n_groups ),
177                symbol_idx=machine.symbol_idx_map[ enter_symbols[ m_idx ] ],
178                pq=None,
179                cross_component=False
180            )
181        ] )
182
183    return 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:
 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    connector_state = CausalState(
 43        name=f"/c", 
 44        classes=set({"connector"}) 
 45    )
 46    
 47    # initial states before adding each machines states
 48    machine.set_states( [ connector_state ] )
 49    machine.start_state = 0
 50
 51    # number of machines (groups of states)
 52    n_groups = len( machines )
 53
 54    # make sure we have enough symbols (otherwise connector can't be unifilar)
 55    if n_groups > len(enter_symbols) :
 56        raise Exception(
 57            f"Too few enter symbols given number of machines"
 58        )
 59
 60    # for each given machine
 61    for m_idx, m in enumerate( machines ) : 
 62
 63        # default to the index of the machine in the list
 64        m_gid = get_gid( m_idx, m.isoclass )
 65
 66        # give the states from this machine a class name
 67        m_classes = { 
 68            f"m_{m_idx}", 
 69            f"isoclass_{m.isoclass}" 
 70        }
 71
 72        added_states = []
 73        
 74        # create a state and extend our existing machine to include it
 75        for s_idx, state in enumerate( m.states ) :
 76
 77            isomorphs=set()
 78            if m.isoclass is not None and m.isoclass in isomorphic_groups :
 79
 80                for other_idx in isomorphic_groups[ m.isoclass ] :
 81                    
 82                    if other_idx == m_idx : 
 83                        continue
 84                    
 85                    other_m = machines[ other_idx ]
 86                    isomorphs.add(  
 87                        rename_state( 
 88                            other_m.states[ s_idx ].name, 
 89                            get_gid( other_idx, other_m.isoclass ) )
 90                    )
 91
 92            added_states.append( 
 93                CausalState( 
 94                    name=rename_state(state.name, m_gid),
 95                    classes=( m_classes | state.classes ),
 96                    isomorphs=isomorphs
 97                ) 
 98            )
 99
100        machine.extend_states( added_states )
101
102        added_transitions = []
103
104        # add all of the transitions from the machine
105        for tr in m.transitions :
106
107            # get the names of the states for the transition
108            origin_state_name = rename_state( m.states[ tr.origin_state_idx ].name, m_gid )
109            target_state_name = rename_state( m.states[ tr.target_state_idx ].name, m_gid )
110
111            # idx of the symbol remaped to this machines alphabet list
112            new_symbol_idx = machine.symbol_idx_map[ m.alphabet[ tr.symbol_idx ] ]
113
114            # create and add the new transition
115            added_transitions.append( Transition(
116                origin_state_idx=machine.state_idx_map[ origin_state_name ],
117                target_state_idx=machine.state_idx_map[ target_state_name ],
118                prob=tr.prob,
119                symbol_idx=new_symbol_idx,
120                pq=None,
121                cross_component=False
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                pq=None,
153                cross_component=False
154            )
155
156        # from m's entry state back to connector
157        escape_pr = 1.0 - mode_residency_factor
158
159        machine.extend_transitions( transitions=[
160            Transition(
161                origin_state_idx=m_entry_state_idx,
162                target_state_idx=machine.start_state,
163                prob=escape_pr,
164                symbol_idx=machine.symbol_idx_map[ exit_symbol ],
165                pq=None,
166                cross_component=False
167            )
168        ] )
169
170        # from the connector to m's entry state
171        machine.extend_alphabet( alphabet=[ enter_symbols[ m_idx ] ] )
172        
173        machine.extend_transitions( transitions=[
174            Transition(
175                origin_state_idx=machine.start_state,
176                target_state_idx=m_entry_state_idx,
177                prob=( 1.0 / n_groups ),
178                symbol_idx=machine.symbol_idx_map[ enter_symbols[ m_idx ] ],
179                pq=None,
180                cross_component=False
181            )
182        ] )
183
184    return machine