|
|
@@ -6,82 +6,138 @@ from sccd.util.bitmap import *
|
|
|
from sccd.util import timer
|
|
|
from sccd.util.visit_tree import *
|
|
|
from sccd.util.freezable import *
|
|
|
+from abc import *
|
|
|
|
|
|
-class State(Freezable):
|
|
|
- __slots__ = ["short_name", "parent", "stable", "children", "default_state", "transitions", "enter", "exit", "opt"]
|
|
|
+@dataclass(eq=False)
|
|
|
+class AbstractState:
|
|
|
+ short_name: str # value of 'id' attribute in XML
|
|
|
+ parent: Optional['AbstractState'] # only None if root state
|
|
|
+ children: List['AbstractState'] = field(default_factory=list)
|
|
|
|
|
|
- def __init__(self, short_name: str, parent: Optional['State']):
|
|
|
- super().__init__()
|
|
|
|
|
|
- self.short_name: str = short_name # value of 'id' attribute in XML
|
|
|
- self.parent: Optional['State'] = parent # only None if root state
|
|
|
+ ####### Calculated values below ########
|
|
|
|
|
|
- self.stable: bool = False # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
|
|
|
+ state_id: int = -1
|
|
|
+ state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
|
|
|
+ full_name: str = ""
|
|
|
+ ancestors: Bitmap = Bitmap()
|
|
|
+ descendants: Bitmap = Bitmap()
|
|
|
|
|
|
- self.children: List['State'] = []
|
|
|
- self.default_state: 'State' = None # child state pointed to by 'initial' attribute
|
|
|
+ effective_targets: Bitmap = Bitmap()
|
|
|
|
|
|
- self.transitions: List['Transition'] = []
|
|
|
+ def __post_init__(self):
|
|
|
+ if self.parent is not None:
|
|
|
+ self.parent.children.append(self)
|
|
|
|
|
|
- self.enter: List[Action] = []
|
|
|
- self.exit: List[Action] = []
|
|
|
+ def __str__(self):
|
|
|
+ return "AbstractState(%s)" % self.short_name
|
|
|
|
|
|
- # Statically computed stuff from tree structure:
|
|
|
- self.opt: Optional['StateStatic'] = None
|
|
|
+ __repr__ = __str__
|
|
|
|
|
|
- if self.parent is not None:
|
|
|
- self.parent.children.append(self)
|
|
|
+@dataclass(eq=False)
|
|
|
+class State(AbstractState):
|
|
|
+ type: 'StateType' = None
|
|
|
|
|
|
- # Subset of descendants that are always entered when this state is the target of a transition, minus history states. Recursive implementation, so better to use self.opt.effective_targets, which is computed statically
|
|
|
- def effective_targets(self) -> List['State']:
|
|
|
- if self.default_state:
|
|
|
- # or-state: this state + recursion on 'default state'
|
|
|
- return [self] + self.default_state.effective_targets()
|
|
|
- else:
|
|
|
- # basic state
|
|
|
- return [self]
|
|
|
+ real_children: List['State'] = field(default_factory=list) # children, but not including pseudo-states such as history
|
|
|
|
|
|
- # States that are always entered when this state is part of the "enter path", but not the actual target of a transition.
|
|
|
- def additional_effective_targets(self, exclude: 'State') -> List['State']:
|
|
|
- return [self]
|
|
|
+ # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
|
|
|
+ stable: bool = False
|
|
|
|
|
|
- def __repr__(self):
|
|
|
- return "State(\"%s\")" % (self.short_name)
|
|
|
+ # Outgoing transitions
|
|
|
+ transitions: List['Transition'] = field(default_factory=list)
|
|
|
|
|
|
-class HistoryState(State):
|
|
|
- __slots__ = ["history_id"]
|
|
|
+ enter: List[Action] = field(default_factory=list)
|
|
|
+ exit: List[Action] = field(default_factory=list)
|
|
|
|
|
|
- def __init__(self, short_name: str, parent: Optional['State']):
|
|
|
- super().__init__(short_name, parent)
|
|
|
+ ####### Calculated values below ########
|
|
|
|
|
|
- def effective_targets(self) -> List['State']:
|
|
|
- return []
|
|
|
+ depth: int = -1 # Root is 0, root's children are 1, and so on
|
|
|
|
|
|
- def additional_effective_targets(self, exclude: 'State') -> List['State']:
|
|
|
- assert False # history state cannot have children and therefore should never occur in a "enter path"
|
|
|
+ # If a direct child of this state is a deep history state, then "deep history" needs to be recorded when exiting this state. This value contains a tuple, with the (history-id, history_mask, history state) of that child state.
|
|
|
+ deep_history: Optional[Tuple[int, Bitmap, 'DeepHistoryState']] = None
|
|
|
+
|
|
|
+ # If a direct child of this state is a shallow history state, then "shallow history" needs to be recorded when exiting this state. This value is the history-id of that child state
|
|
|
+ shallow_history: Optional[Tuple[int, 'ShallowHistoryState']] = None
|
|
|
+
|
|
|
+ # Triggers of outgoing transitions that are AfterTrigger.
|
|
|
+ after_triggers: List['AfterTrigger'] = field(default_factory=list)
|
|
|
+
|
|
|
+ def __post_init__(self):
|
|
|
+ super().__post_init__()
|
|
|
+ if self.parent is not None:
|
|
|
+ self.parent.real_children.append(self)
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ if isinstance(self.type, AndState):
|
|
|
+ return "AndState(%s)" % self.short_name
|
|
|
+ elif isinstance(self.type, OrState):
|
|
|
+ return "OrState(%s)" % self.short_name
|
|
|
+ else:
|
|
|
+ return "State?(%s)" % self.short_name
|
|
|
|
|
|
+ __repr__ = __str__
|
|
|
+
|
|
|
+
|
|
|
+@dataclass(eq=False)
|
|
|
+class HistoryState(AbstractState):
|
|
|
+ history_id: int = -1
|
|
|
+
|
|
|
+ def __post_init__(self):
|
|
|
+ super().__post_init__()
|
|
|
+ self.type = HistoryStateType(self)
|
|
|
+
|
|
|
+@dataclass(eq=False)
|
|
|
class ShallowHistoryState(HistoryState):
|
|
|
+ def __str__(self):
|
|
|
+ return "ShallowHistoryState(%s)" % self.short_name
|
|
|
+ __repr__ = __str__
|
|
|
|
|
|
- def __repr__(self):
|
|
|
- return "ShallowHistoryState(\"%s\")" % (self.short_name)
|
|
|
|
|
|
+@dataclass(eq=False)
|
|
|
class DeepHistoryState(HistoryState):
|
|
|
+ def __str__(self):
|
|
|
+ return "DeepHistoryState(%s)" % self.short_name
|
|
|
+ __repr__ = __str__
|
|
|
+
|
|
|
+@dataclass(eq=False)
|
|
|
+class StateType(ABC):
|
|
|
+ state: State
|
|
|
|
|
|
- def __repr__(self):
|
|
|
- return "DeepHistoryState(\"%s\")" % (self.short_name)
|
|
|
+ @abstractmethod
|
|
|
+ def effective_targets(self):
|
|
|
+ pass
|
|
|
+
|
|
|
+ @abstractmethod
|
|
|
+ def additional_effective_targets(self, exclude: 'State'):
|
|
|
+ pass
|
|
|
|
|
|
-class ParallelState(State):
|
|
|
+@dataclass(eq=False)
|
|
|
+class AndState(StateType):
|
|
|
|
|
|
- def effective_targets(self) -> List['State']:
|
|
|
- # this state + recursive on all children that are not a history state
|
|
|
- return self.additional_effective_targets(exclude=None)
|
|
|
+ def effective_targets(self):
|
|
|
+ return self.additional_effective_targets(None)
|
|
|
|
|
|
- def additional_effective_targets(self, exclude: 'State') -> List['State']:
|
|
|
- # this state + all effective targets of children, except for 'excluded state' and history states
|
|
|
- return [self] + list(itertools.chain(*(c.effective_targets() for c in self.children if not isinstance(c, HistoryState) and c is not exclude)))
|
|
|
+ def additional_effective_targets(self, exclude: 'State'):
|
|
|
+ return [self.state] + list(itertools.chain(*(c.type.effective_targets() for c in self.state.children if not isinstance(c, HistoryState) and c is not exclude)))
|
|
|
|
|
|
- def __repr__(self):
|
|
|
- return "ParallelState(\"%s\")" % (self.short_name)
|
|
|
+@dataclass(eq=False)
|
|
|
+class OrState(StateType):
|
|
|
+ default_state: AbstractState
|
|
|
+
|
|
|
+ def effective_targets(self):
|
|
|
+ return [self.state] + self.default_state.type.effective_targets()
|
|
|
+
|
|
|
+ def additional_effective_targets(self, exclude: 'State'):
|
|
|
+ return [self.state]
|
|
|
+
|
|
|
+@dataclass(eq=False)
|
|
|
+class HistoryStateType(StateType):
|
|
|
+
|
|
|
+ def effective_targets(self):
|
|
|
+ return []
|
|
|
+
|
|
|
+ def additional_effective_targets(self, exclude: 'State'):
|
|
|
+ assert False # history state cannot have children and therefore should never occur in a "enter path"
|
|
|
|
|
|
@dataclass
|
|
|
class EventDecl:
|
|
|
@@ -171,79 +227,40 @@ class AfterTrigger(Trigger):
|
|
|
|
|
|
EMPTY_TRIGGER = Trigger(enabling=[])
|
|
|
|
|
|
-class Transition(Freezable):
|
|
|
- __slots__ = ["source", "target", "scope", "target_string", "guard", "actions", "trigger", "opt"]
|
|
|
-
|
|
|
- def __init__(self, source: State, target: State, scope: Scope, target_string: Optional[str] = None):
|
|
|
- super().__init__()
|
|
|
+@dataclass(eq=False)
|
|
|
+class Transition:
|
|
|
+ source: State
|
|
|
+ target_string: Optional[str]
|
|
|
+ scope: Scope
|
|
|
|
|
|
- self.source: State = source
|
|
|
- self.target: State = target
|
|
|
- self.scope: Scope = scope
|
|
|
+ target: State = None
|
|
|
|
|
|
- self.target_string: Optional[str] = target_string
|
|
|
+ guard: Optional[Expression] = None
|
|
|
+ actions: List[Action] = field(default_factory=list)
|
|
|
+ trigger: Trigger = EMPTY_TRIGGER
|
|
|
|
|
|
- self.guard: Optional[Expression] = None
|
|
|
- self.actions: List[Action] = []
|
|
|
- self.trigger: Trigger = EMPTY_TRIGGER
|
|
|
+ ####### CALCULATED VALUES ########
|
|
|
|
|
|
- # Statically computed stuff from tree structure:
|
|
|
- self.opt: Optional['TransitionStatic'] = None
|
|
|
+ id: int = None # just a unique number, >= 0
|
|
|
+ exit_mask: State = None
|
|
|
+ arena: State = None
|
|
|
+ arena_bitmap: Bitmap = None
|
|
|
+ # The "enter set" can be computed partially statically, or entirely statically if there are no history states in it.
|
|
|
+ enter_states_static: Bitmap = None
|
|
|
+ target_history_id: Optional[int] = None # History ID if target of transition is a history state, otherwise None.
|
|
|
+ target_stable: bool = None # Whether target state is a stable state
|
|
|
+ raised_events: Bitmap = None # (internal) event IDs raised by this transition
|
|
|
|
|
|
def __str__(self):
|
|
|
- return termcolor.colored("%s -> %s" % (self.source.opt.full_name, self.target.opt.full_name), 'green')
|
|
|
+ if self.target is None:
|
|
|
+ return "Transition(%s -> %s)" % (self.source.short_name, self.target_string)
|
|
|
+ else:
|
|
|
+ return termcolor.colored("%s -> %s" % (self.source.full_name, self.target.full_name), 'green')
|
|
|
|
|
|
__repr__ = __str__
|
|
|
|
|
|
|
|
|
-# Simply a collection of read-only fields, generated during "optimization" for each state, inferred from the model, i.e. the hierarchy of states and transitions
|
|
|
-class StateStatic(Freezable):
|
|
|
- __slots__ = ["full_name", "depth", "state_id", "state_id_bitmap", "ancestors", "descendants", "effective_targets", "deep_history", "shallow_history", "after_triggers"]
|
|
|
- def __init__(self):
|
|
|
- super().__init__()
|
|
|
-
|
|
|
- self.full_name: str = ""
|
|
|
-
|
|
|
- self.depth: int -1 # Root is 0, root's children are 1, and so on
|
|
|
- self.state_id: int = -1
|
|
|
- self.state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
|
|
|
-
|
|
|
- self.ancestors: Bitmap = Bitmap()
|
|
|
- self.descendants: Bitmap = Bitmap()
|
|
|
-
|
|
|
- self.effective_targets: Bitmap = Bitmap()
|
|
|
-
|
|
|
- # If a direct child of this state is a deep history state, then "deep history" needs to be recorded when exiting this state. This value contains a tuple, with the (history-id, history_mask, history state) of that child state.
|
|
|
- self.deep_history: Optional[Tuple[int, Bitmap, DeepHistoryState]] = None
|
|
|
-
|
|
|
- # If a direct child of this state is a shallow history state, then "shallow history" needs to be recorded when exiting this state. This value is the history-id of that child state
|
|
|
- self.shallow_history: Optional[Tuple[int, ShallowHistoryState]] = None
|
|
|
-
|
|
|
- # Triggers of outgoing transitions that are AfterTrigger.
|
|
|
- self.after_triggers: List[AfterTrigger] = []
|
|
|
-
|
|
|
-
|
|
|
-# Data that is generated for each transition.
|
|
|
-class TransitionStatic(Freezable):
|
|
|
- __slots__ = ["id", "exit_mask", "arena", "arena_bitmap", "enter_states_static", "target_history_id", "target_stable", "raised_events"]
|
|
|
-
|
|
|
- def __init__(self, id: int, exit_mask: Bitmap, arena: State, arena_bitmap: Bitmap, enter_states_static: Bitmap, target_history_id: Optional[int], target_stable: bool, raised_events: Bitmap):
|
|
|
- super().__init__()
|
|
|
- self.id: int = id # just a unique number, >= 0
|
|
|
- self.exit_mask: State = exit_mask
|
|
|
- self.arena: State = arena
|
|
|
- self.arena_bitmap: Bitmap = arena_bitmap
|
|
|
- # The "enter set" can be computed partially statically, or entirely statically if there are no history states in it.
|
|
|
- self.enter_states_static: Bitmap = enter_states_static
|
|
|
- self.target_history_id: Optional[int] = target_history_id # History ID if target of transition is a history state, otherwise None.
|
|
|
- self.target_stable: bool = target_stable # Whether target state is a stable state
|
|
|
- self.raised_events: Bitmap = raised_events # (internal) event IDs raised by this transition
|
|
|
- self.freeze()
|
|
|
-
|
|
|
-
|
|
|
-class StateTree(Freezable):
|
|
|
- __slots__ = ["root", "transition_list", "state_list", "state_dict", "timer_count", "history_states", "initial_history_values", "initial_states"]
|
|
|
-
|
|
|
+class StateTree:
|
|
|
def __init__(self, root: State):
|
|
|
super().__init__()
|
|
|
self.root: State = root
|
|
|
@@ -260,11 +277,9 @@ class StateTree(Freezable):
|
|
|
def assign_state_id():
|
|
|
next_id = 0
|
|
|
def f(state: State, _=None):
|
|
|
- state.opt = StateStatic()
|
|
|
-
|
|
|
nonlocal next_id
|
|
|
- state.opt.state_id = next_id
|
|
|
- state.opt.state_id_bitmap = bit(next_id)
|
|
|
+ state.state_id = next_id
|
|
|
+ state.state_id_bitmap = bit(next_id)
|
|
|
next_id += 1
|
|
|
|
|
|
return f
|
|
|
@@ -276,63 +291,56 @@ class StateTree(Freezable):
|
|
|
full_name = '/' + state.short_name
|
|
|
else:
|
|
|
full_name = parent_full_name + '/' + state.short_name
|
|
|
- state.opt.full_name = full_name
|
|
|
+ state.full_name = full_name
|
|
|
return full_name
|
|
|
|
|
|
def assign_depth(state: State, parent_depth: int = 0):
|
|
|
- state.opt.depth = parent_depth + 1
|
|
|
+ state.depth = parent_depth + 1
|
|
|
return parent_depth + 1
|
|
|
|
|
|
def add_to_list(state: State ,_=None):
|
|
|
- self.state_dict[state.opt.full_name] = state
|
|
|
+ self.state_dict[state.full_name] = state
|
|
|
self.state_list.append(state)
|
|
|
|
|
|
def visit_transitions(state: State, _=None):
|
|
|
for t in state.transitions:
|
|
|
self.transition_list.append(t)
|
|
|
if t.trigger and isinstance(t.trigger, AfterTrigger):
|
|
|
- state.opt.after_triggers.append(t.trigger)
|
|
|
+ state.after_triggers.append(t.trigger)
|
|
|
self.timer_count += 1
|
|
|
|
|
|
def set_ancestors(state: State, ancestors=Bitmap()):
|
|
|
- state.opt.ancestors = ancestors
|
|
|
- return ancestors | state.opt.state_id_bitmap
|
|
|
+ state.ancestors = ancestors
|
|
|
+ return ancestors | state.state_id_bitmap
|
|
|
|
|
|
def set_descendants(state: State, children_descendants):
|
|
|
descendants = bm_union(children_descendants)
|
|
|
- state.opt.descendants = descendants
|
|
|
- return state.opt.state_id_bitmap | descendants
|
|
|
+ state.descendants = descendants
|
|
|
+ return state.state_id_bitmap | descendants
|
|
|
|
|
|
def calculate_effective_targets(state: State, _=None):
|
|
|
# implementation of "effective_targets"-method is recursive (slow)
|
|
|
# store the result, it is always the same:
|
|
|
- state.opt.effective_targets = states_to_bitmap(state.effective_targets())
|
|
|
+ state.effective_targets = states_to_bitmap(state.type.effective_targets())
|
|
|
|
|
|
- history_ids = {}
|
|
|
def deal_with_history(state: State, children_history):
|
|
|
for h in children_history:
|
|
|
if isinstance(h, DeepHistoryState):
|
|
|
- state.opt.deep_history = (history_ids[h], h.parent.opt.descendants, h)
|
|
|
+ state.deep_history = (h.history_id, h.parent.descendants, h)
|
|
|
elif isinstance(h, ShallowHistoryState):
|
|
|
- state.opt.shallow_history = (history_ids[h], h)
|
|
|
+ state.shallow_history = (h.history_id, h)
|
|
|
|
|
|
if isinstance(state, HistoryState):
|
|
|
- history_ids[state] = len(self.initial_history_values) # generate history ID
|
|
|
+ state.history_id = len(self.initial_history_values) # generate history ID
|
|
|
self.history_states.append(state)
|
|
|
- self.initial_history_values.append(state.parent.opt.effective_targets)
|
|
|
+ self.initial_history_values.append(state.parent.effective_targets)
|
|
|
return state
|
|
|
|
|
|
- def freeze(state: State, _=None):
|
|
|
- state.freeze()
|
|
|
- state.opt.freeze()
|
|
|
-
|
|
|
visit_tree(root, lambda s: s.children,
|
|
|
parent_first=[
|
|
|
assign_state_id(),
|
|
|
assign_full_name,
|
|
|
- assign_depth,
|
|
|
add_to_list,
|
|
|
- visit_transitions,
|
|
|
set_ancestors,
|
|
|
],
|
|
|
child_first=[
|
|
|
@@ -340,19 +348,34 @@ class StateTree(Freezable):
|
|
|
calculate_effective_targets,
|
|
|
])
|
|
|
|
|
|
- self.initial_states = root.opt.effective_targets
|
|
|
+ visit_tree(root, lambda s: s.real_children,
|
|
|
+ parent_first=[
|
|
|
+ assign_depth,
|
|
|
+ visit_transitions,
|
|
|
+ ],
|
|
|
+ child_first=[])
|
|
|
+
|
|
|
+ self.initial_states = root.effective_targets
|
|
|
|
|
|
visit_tree(root, lambda s: s.children,
|
|
|
child_first=[
|
|
|
deal_with_history,
|
|
|
- freeze,
|
|
|
])
|
|
|
|
|
|
+ # print()
|
|
|
+ # def pretty_print(s, indent=""):
|
|
|
+ # print(indent, s)
|
|
|
+ # return indent + " "
|
|
|
+ # visit_tree(root, lambda s: s.children,
|
|
|
+ # parent_first=[
|
|
|
+ # pretty_print,
|
|
|
+ # ])
|
|
|
+
|
|
|
for t_id, t in enumerate(self.transition_list):
|
|
|
# Arena can be computed statically. First compute Lowest-common ancestor:
|
|
|
arena = self.lca(t.source, t.target)
|
|
|
# Arena must be an Or-state:
|
|
|
- while isinstance(arena, (ParallelState, HistoryState)):
|
|
|
+ while not isinstance(arena.type, OrState):
|
|
|
arena = arena.parent
|
|
|
|
|
|
# Exit states can be efficiently computed at runtime based on the set of current states.
|
|
|
@@ -363,7 +386,7 @@ class StateTree(Freezable):
|
|
|
# Enter path is the intersection between:
|
|
|
# 1) the transition's target and its ancestors, and
|
|
|
# 2) the arena's descendants
|
|
|
- enter_path = (t.target.opt.state_id_bitmap | t.target.opt.ancestors) & arena.opt.descendants
|
|
|
+ enter_path = (t.target.state_id_bitmap | t.target.ancestors) & arena.descendants
|
|
|
# All states on the enter path will be entered, but on the enter path, there may also be AND-states whose children are not on the enter path, but should also be entered.
|
|
|
enter_path_iter = self.bitmap_to_states(enter_path)
|
|
|
entered_state = next(enter_path_iter, None)
|
|
|
@@ -372,34 +395,33 @@ class StateTree(Freezable):
|
|
|
next_entered_state = next(enter_path_iter, None)
|
|
|
if next_entered_state:
|
|
|
# an intermediate state on the path from arena to target
|
|
|
- enter_states_static |= states_to_bitmap(entered_state.additional_effective_targets(next_entered_state))
|
|
|
+ enter_states_static |= states_to_bitmap(entered_state.type.additional_effective_targets(next_entered_state))
|
|
|
else:
|
|
|
# the actual target of the transition
|
|
|
- enter_states_static |= entered_state.opt.effective_targets
|
|
|
+ enter_states_static |= entered_state.effective_targets
|
|
|
entered_state = next_entered_state
|
|
|
|
|
|
target_history_id = None
|
|
|
if isinstance(t.target, HistoryState):
|
|
|
- target_history_id = history_ids[t.target]
|
|
|
+ target_history_id = t.target.history_id
|
|
|
+
|
|
|
+ target_stable = False
|
|
|
+ if isinstance(t.target, State):
|
|
|
+ target_stable = t.target.stable
|
|
|
|
|
|
raised_events = Bitmap()
|
|
|
for a in t.actions:
|
|
|
if isinstance(a, RaiseInternalEvent):
|
|
|
raised_events |= bit(a.event_id)
|
|
|
|
|
|
- t.opt = TransitionStatic(
|
|
|
- id=t_id,
|
|
|
- exit_mask=arena.opt.descendants,
|
|
|
- arena=arena,
|
|
|
- arena_bitmap=arena.opt.descendants | arena.opt.state_id_bitmap,
|
|
|
- enter_states_static=enter_states_static,
|
|
|
- target_history_id=target_history_id,
|
|
|
- target_stable=t.target.stable,
|
|
|
- raised_events=raised_events)
|
|
|
-
|
|
|
- t.freeze()
|
|
|
-
|
|
|
- self.freeze()
|
|
|
+ t.id = t_id
|
|
|
+ t.exit_mask = arena.descendants
|
|
|
+ t.arena = arena
|
|
|
+ t.arena_bitmap = arena.descendants | arena.state_id_bitmap
|
|
|
+ t.enter_states_static = enter_states_static
|
|
|
+ t.target_history_id = target_history_id
|
|
|
+ t.target_stable = target_stable
|
|
|
+ t.raised_events = raised_events
|
|
|
|
|
|
def bitmap_to_states(self, bitmap: Bitmap) -> Iterator[State]:
|
|
|
return (self.state_list[id] for id in bm_items(bitmap))
|
|
|
@@ -409,10 +431,10 @@ class StateTree(Freezable):
|
|
|
|
|
|
def lca(self, s1: State, s2: State) -> State:
|
|
|
# Intersection between source & target ancestors, last member in depth-first sorted state list.
|
|
|
- return self.state_list[bm_highest_bit(s1.opt.ancestors & s2.opt.ancestors)]
|
|
|
+ return self.state_list[bm_highest_bit(s1.ancestors & s2.ancestors)]
|
|
|
|
|
|
def states_to_bitmap(states: Iterable[State]) -> Bitmap:
|
|
|
- return bm_from_list(s.opt.state_id for s in states)
|
|
|
+ return bm_from_list(s.state_id for s in states)
|
|
|
|
|
|
def is_ancestor(parent: State, child: State) -> bool:
|
|
|
- return bm_has(child.opt.ancestors, parent.opt.state_id)
|
|
|
+ return bm_has(child.ancestors, parent.state_id)
|