Tutorial: ActionArchitecture
This tutorial covers use of use of ActionArchitecture to define human/autonomous systems behaviors the Function class. ActionArchitecture is useful for representing a Function’s progress through an action sequence graph, the different actions/tasks to be performed by a function (e.g., modes of operation, etc.).
::: {.content-hidden}
Copyright © 2024, United States Government, as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.
The “"Fault Model Design tools - fmdtools version 2"” software is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
:::
#| echo: true
import fmdtools.sim.propagate as prop
import inspect
Action architectures are used within a function block to represent Action Sequence Graphs - actions that the function performs and their sequence. Actions in an ActionArchitecture are respresented by Action blocks, which are similar to function and component blocks in that they have
flow connections
modes, and
behaviors
Flow connections are routed to the action in the function block definition and represent the shared variables between the actions.
Modes are similar to function modes and are instantiated (as they are in components) at both the Function and Action level. Using the name= option enables one to tag these modes as action modes at the function level while using the same local name.
Below we define the states to performed by the ASG
Outcome, which tracks the number of actions and perceptions performed by the ASG
HazardState, which specifies whether a hazard is present, percieved, or mitigated
#| echo: true
from model_main import OutcomeStates, Outcome, HazardState, Hazard
print(inspect.getsource(OutcomeStates))
print(inspect.getsource(Outcome))
print(inspect.getsource(HazardState))
print(inspect.getsource(Hazard))
class OutcomeStates(State):
"""State tracking the number of actions and perceptions performed."""
num_perceptions: int = 0
num_actions: int = 0
class Outcome(Flow):
container_s = OutcomeStates
class HazardState(State):
"""Whether a hazard is present, percieved, or mitigated by the human."""
present: bool = False
percieved: bool = False
mitigated: bool = False
class Hazard(Flow):
container_s = HazardState
Below we define three actions for use in a given model
Perceive, a user’s perception abilities/behaviors. In this function the user perceives a hazard (unless their perception fails)
Act, the user’s action which they perform to mitigate the hazard.
Done, the user’s state when they are done performing the action.
#| echo: true
from fmdtools_examples.human_hazard_mitigation.model_main import Perceive, Act, Done
print(inspect.getsource(Perceive))
print(inspect.getsource(Act))
print(inspect.getsource(Done))
class Perceive(Action):
"""A user's perception abilities/behaviors for percieving the hazard."""
container_m = ActionMode
flow_hazard = Hazard
flow_outcome = Outcome
def dynamic_behavior(self):
if not self.m.in_mode('failed', 'unable'):
self.hazard.s.percieved = self.hazard.s.present
self.outcome.s.num_perceptions += self.hazard.s.percieved
else:
self.hazard.s.percieved = False
self.m.remove_fault('failed', 'nom')
def percieved(self):
return self.hazard.s.percieved
class Act(Action):
"""User actions to mitigate the hazard."""
container_m = ActionMode
flow_hazard = Hazard
flow_outcome = Outcome
def dynamic_behavior(self):
if not self.m.in_mode('failed', 'unable'):
self.outcome.s.num_actions += 1
self.hazard.s.mitigated = True
elif self.m.in_mode('failed'):
self.hazard.s.mitigated = False
self.m.remove_fault('failed', 'nom')
else:
self.hazard.s.mitigated = False
def acted(self):
return not self.m.in_mode('failed')
class Done(Action):
"""User state after performing the action."""
flow_hazard = Hazard
def dynamic_behavior(self):
if not self.hazard.s.present:
self.hazard.s.mitigated = False
def ready(self):
return not self.hazard.s.present
To proceed through the sequence of actions, conditions must be met between each action. In these actions, we have defined the following conditions
Percieve.percieved… perception is done if the hazard is percieved
Act.acted… the action is complete if the action was performed
Done.complete… the hazard mitigation is over (and mitigated state is reset to False)
To create the overall ASG structure, the following adds the flows, actions, and conditions to the ActionArchitecture class
#| echo: true
from model_main import Human
print(inspect.getsource(Human))
class Human(ActionArchitecture):
"""Overall human action sequence graph specifying what the user will do when."""
initial_action = "perceive"
def init_architecture(self, *args, **kwargs):
# flows from external fxn/model can be defined here,
self.add_flow("hazard", Hazard)
# along with flows internal to the ASG class
self.add_flow("outcome", fclass=Outcome)
self.add_act("perceive", Perceive, "outcome", "hazard")
self.add_act("act", Act, "outcome", "hazard")
self.add_act("done", Done, "hazard")
self.add_cond("perceive", "act", "percieved", self.acts['perceive'].percieved)
self.add_cond("act", "done", "acted", self.acts['act'].acted)
self.add_cond("done", "perceive", "ready", self.acts['done'].ready)
Note the use of the following methods
add_flow adds a flow so it can pass variables between actions. Here Outcome is an internal flow, while Hazard is an external flow.
#| echo: true
from fmdtools.define.architecture.action import ActionArchitecture
help(ActionArchitecture.add_flow)
Help on function add_flow in module fmdtools.define.architecture.base:
add_flow(self, name, fclass=<class 'fmdtools.define.flow.base.Flow'>, **kwargs)
Add a flow with given attributes to the model.
Parameters
----------
name : str
Unique flow name to give the flow in the model
fclass : Class, optional
Class to instantiate (e.g. CommsFlow, MultiFlow). Default is Flow.
Class must take flowname, p, s as input to __init__()
May alternatively provide already-instanced object.
kwargs: kwargs
Dicts for non-default values to p, s, etc
add_act adds the action to the function and hands it the given flows and parameters. Here the actions are “Percieve”, “Act”, and “Done”
#| echo: true
help(ActionArchitecture.add_act)
Help on function add_act in module fmdtools.define.architecture.action:
add_act(self, name, actclass, *flownames, **fkwargs)
Associate an Action with the architecture. Called after add_flow.
Parameters
----------
name : str
Internal Name for the Action
actclass : Action
Action class to instantiate
*flownames : flow
Flows (optional) which connect the actions
duration:
Duration of the action. Default is 0.0
**kwargs : any
kwargs to instantiate the Action with.
add_cond specifies the conditions for going from one action to another.
#| echo: true
help(ActionArchitecture.add_cond)
Help on function add_cond in module fmdtools.define.architecture.action:
add_cond(self, start_action, end_action, name='auto', condition='pass')
Associate a Condition with the ActionArchitecture.
Conditions specify when to precede from one action to the next.
Parameters
----------
start_action : str
Action where the condition is checked
end_action : str
Action that the condition leads to.
name : str
Name for the condition.
Defaults to numbered conditions if none are provided.
condition : method
Method in the class to use as a condition.
Defaults to self.condition_pass if none are provided.
ASG.build finally constructs the structure of the ASG (see self.action_graph and self.flow_graph) and determines the settings for the simulation. In DetectHazard, default options are used, with the first action specified as “Percieve” and also with it specified that the actions propagate in the dynamic step (rather than static step)
#| echo: true
help(ActionArchitecture.build)
Help on function build in module fmdtools.define.architecture.action:
build(self, construct_graph=False, **kwargs)
Build the action graph.
We can look at the attributes of the ASG by instantiating it
#| echo: true
h = Human()
h
human Human
- t=Time(time=-0.1, timers={})
- m=Mode(mode='nominal', faults=set(), sub_faults=False)
FLOWS:
- hazard=Hazard(s=(present=False, percieved=False, mitigated=False))
- outcome=Outcome(s=(num_perceptions=0, num_actions=0))
ACTS:
- perceive=Perceive(m=(mode='nominal', faults=set(), sub_faults=False))
- act=Act(m=(mode='nominal', faults=set(), sub_faults=False))
- done=Done()
CONDS:
- percieved=<method perceive.percieved()>
- acted=<method act.acted()>
- ready=<method done.ready()>
ActionArchitectureGraph can also be used to visualize the flow of actions (e.g., which are active, flows vs conditions, etc.).
This graph can also be generated using ActionArchitecture.as_modelgraph()
#| echo: true
from fmdtools.define.architecture.action import ActionArchitectureGraph
#| echo: true
ag = ActionArchitectureGraph(h)
#| echo: true
fig, ax = ag.draw()
As shown, the “Percieve” action is active (green), while the inactive actions are shown in blue. This action is active because it was defined as initial_action in the ASG definition.
These are stored as attributes in the underlying graph structure
#| echo: true
ag.g.nodes['perceive']
{'bipartite': 0, 'nodetype': 'Action', 'active': True}
Function-level Simulation
ActionArchitectures must be instantiated within a Function in order to be simulable with methods in propagate. Below we instantiate a Function and show how an ActionArchitecture can be simulated independent of other Model attributes.
#| echo: true
from model_main import DetectHazard
print(inspect.getsource(DetectHazard))
class DetectHazard(Function):
"""Function containing the human."""
container_m = Mode
arch_aa = Human
flow_hazard = Hazard
If we update the action, we can see the ASG progress between states…
#| echo: true
ex_fxn = DetectHazard('detect_hazard')
ex_fxn.t.dt=1.0
ex_fxn.aa.flows['hazard']
hazard Hazard
- s=HazardState(present=False, percieved=False, mitigated=False)
#| echo: true
ex_fxn.aa.flows['hazard'].s.present=True
ex_fxn(time= 1, proptype='dynamic')
#| echo: true
ag = ActionArchitectureGraph(ex_fxn.aa)
ag.draw()
(<Figure size 1200x1000 with 1 Axes>, <Axes: >)
#| echo: true
ex_fxn.aa.flows['hazard']
hazard Hazard
- s=HazardState(present=True, percieved=True, mitigated=True)
#| echo: true
assert ex_fxn.aa.flows['hazard'].s.present
#| echo: true
ex_fxn.aa.flows['outcome']
outcome Outcome
- s=OutcomeStates(num_perceptions=1, num_actions=1)
#| echo: true
assert ex_fxn.aa.flows['outcome'].s.num_perceptions==1
assert ex_fxn.aa.flows['outcome'].s.num_actions==1
As shown, each of the actions are progressed throuh in a single timestep until the ASG is in the “Done” action
#| echo: true
ex_fxn.aa.flows['hazard'].s.present=False
ex_fxn(time=2, proptype='dynamic')
#| echo: true
ag = ActionArchitectureGraph(ex_fxn.aa)
fig, ax = ag.draw()
As shown, now that the hazard is no longer present, the “Ready” Condition is triggered and the ASG goes back to the percieve state.
#| echo: true
ex_fxn.aa.flows['outcome']
outcome Outcome
- s=OutcomeStates(num_perceptions=1, num_actions=1)
#| echo: true
ex_fxn.aa.flows['hazard']
hazard Hazard
- s=HazardState(present=False, percieved=False, mitigated=False)
#| echo: true
ex_fxn.hazard
hazard Hazard
- s=HazardState(present=False, percieved=False, mitigated=False)
#| echo: true
assert ex_fxn.aa.flows['hazard'].s.present==False
assert ex_fxn.aa.flows['hazard'].s.mitigated==False
assert id(ex_fxn.hazard) == id(ex_fxn.aa.flows['hazard'])
This is essentially what a step-though of a simulation looks like. In practice, we simulate these behaviors using propagate as long as the ActionArchitecture is contained in a Function. Here we use the disturbances argument to simulate an external hazard occuring at t=5
#| echo: true
result_indiv, hist_indiv = prop.sequence(ex_fxn, disturbances={5: {'aa.flows.hazard.s.present': True}}, include_nominal=False)
#| echo: true
fig, axs = hist_indiv.plot_line('aa.flows.hazard.s.present',
'aa.flows.hazard.s.percieved',
'aa.flows.hazard.s.mitigated', figsize=(10,5))
#| echo: true
hist_indiv.aa.active_actions
array([{'perceive'}, {'perceive'}, {'perceive'}, {'perceive'},
{'perceive'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}, {'done'}, {'done'}, {'done'}, {'done'}, {'done'},
{'done'}], dtype=object)
#| echo: true
assert hist_indiv.aa.flows.hazard.s.mitigated[5]
assert hist_indiv.aa.active_actions[5]=={'done'}
Model Simulation
Below, this function is placed in the context of a FunctionalArchitecture model so we can see how it behaves in the context of its interactions.
#| echo: true
from model_main import ProduceHazard, PassStates, PassHazard, HazardModel
print(inspect.getsource(ProduceHazard))
print(inspect.getsource(PassStates))
print(inspect.getsource(PassHazard))
print(inspect.getsource(HazardModel))
class ProduceHazard(Function):
"""Function producing Hazards."""
flow_hazard = Hazard
def dynamic_behavior(self):
if not self.t.time % 4:
self.hazard.s.present = True
else:
self.hazard.s.present = False
class PassStates(State):
"""Whether or not the hazard is ultimately passed or mitigated."""
hazards_mitigated: int = 0
hazards_propagated: int = 0
class PassHazard(Function):
"""Accumulates total hazards/mitigations."""
container_s = PassStates
flow_hazard = Hazard
def dynamic_behavior(self):
if self.hazard.s.present and self.hazard.s.mitigated:
self.s.hazards_mitigated += 1
elif self.hazard.s.present and not self.hazard.s.mitigated:
self.s.hazards_propagated += 1
class HazardModel(FunctionArchitecture):
"""Overall model of the human in context."""
default_sp = dict(end_time=60, dt=1.0)
def init_architecture(self, **kwargs):
self.add_flow("hazard", Hazard)
self.add_fxn("produce_hazard", ProduceHazard, 'hazard')
self.add_fxn("detect_hazard", DetectHazard, 'hazard')
self.add_fxn("pass_hazard", PassHazard, 'hazard')
As shown, this model connects the detect_hazard with a detect_hazard function, which are used (1) to load the system and then to (2) tabulate the number of mitigations.
Below we show how this translates into simulation results
#| echo: true
mdl = HazardModel()
endstate, mdlhist = prop.nominal(mdl)
Below we look at the states of the functions/flows to see how this has simulated.
#| echo: true
mdlhist
time: array(61)
flows.hazard.s.present: array(61)
flows.hazard.s.percieved: array(61)
flows.hazard.s.mitigated: array(61)
fxns.detect_hazard.aa.active_actions: array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.aa.acts.act.m.mode: array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.a array(61)
fxns.detect_hazard.m.sub_faults: array(61)
fxns.pass_hazard.s.hazards_mitigated: array(61)
fxns.pass_hazard.s.hazards_propagated: array(61)
#| echo: true
restab = mdlhist.get('fxns.detect_hazard.aa.active_actions', 'flows.hazard.s').as_table()
#| echo: true
restab
| fxns.detect_hazard.aa.active_actions | flows.hazard.s.present | flows.hazard.s.percieved | flows.hazard.s.mitigated | |
|---|---|---|---|---|
| 0 | {perceive} | False | False | False |
| 1 | {perceive} | False | False | False |
| 2 | {perceive} | False | False | False |
| 3 | {perceive} | False | False | False |
| 4 | {done} | True | True | True |
| ... | ... | ... | ... | ... |
| 56 | {done} | True | True | True |
| 57 | {perceive} | False | False | False |
| 58 | {perceive} | False | False | False |
| 59 | {perceive} | False | False | False |
| 60 | {done} | True | True | True |
61 rows × 4 columns
As shown, the ASG alternates between Perceive (when the hazard is not present) and Done (when the hazard is present). As a result, all of the present hazards (above) are also perceived and mitigated, and no hazards are propagated.
Or, in plot form…
#| echo: true
fig, axs = mdlhist.plot_line('fxns.pass_hazard.s.hazards_mitigated',
'fxns.pass_hazard.s.hazards_propagated',
'flows.hazard.s.present',
'flows.hazard.s.percieved',
'flows.hazard.s.mitigated', figsize=(10,5))
As shown, perceptions and actions track the hazards mitigated.
Fault Simulation
Below we will simulate a fault and see how it tracks in the model.
#| echo: true
result_fault, mdlhist_fault = prop.one_fault(mdl, 'detect_hazard.aa.acts.perceive','failed', time=4, to_return='graph')
#| echo: true
mdlhist_fault.faulty.fxns.detect_hazard.aa.acts.perceive.m.mode
array(['nominal', 'nominal', 'nominal', 'nominal', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom',
'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom', 'nom'],
dtype='<U7')
As shown, this fault results in the hazard not being perceived (and thus the hazard propagating)
#| echo: true
fig, axs = mdlhist_fault.plot_line('fxns.pass_hazard.s.hazards_mitigated',
'fxns.pass_hazard.s.hazards_propagated',
'flows.hazard.s.present',
'flows.hazard.s.percieved',
'flows.hazard.s.mitigated', figsize=(10,5), time_slice=[4])
As shown, this only shows up in the PassHazard function (since the fault is removed in one timestep).
#| echo: true
fig, ax = result_fault.get_faulty().tend.graph.draw()
To see this in more detail, we will process the results history and then use graph.results_from at the time of the fault. Note how the model histories can be processed below.
#| echo: true
reshist = mdlhist_fault.get_degraded_hist(*mdl.fxns, *mdl.flows)
reshist.as_table()
| detect_hazard | pass_hazard | hazard | total | time | |
|---|---|---|---|---|---|
| 0 | False | False | False | 0 | 0.0 |
| 1 | False | False | False | 0 | 1.0 |
| 2 | False | False | False | 0 | 2.0 |
| 3 | False | False | False | 0 | 3.0 |
| 4 | True | True | True | 3 | 4.0 |
| ... | ... | ... | ... | ... | ... |
| 56 | True | True | True | 3 | 56.0 |
| 57 | True | True | True | 3 | 57.0 |
| 58 | True | True | True | 3 | 58.0 |
| 59 | True | True | True | 3 | 59.0 |
| 60 | True | True | True | 3 | 60.0 |
61 rows × 5 columns
#| echo: true
reshist = mdlhist_fault.get_faulty_hist(*mdl.fxns, *mdl.flows)
reshist.as_table()
| detect_hazard | total | time | |
|---|---|---|---|
| 0 | False | 0 | 0.0 |
| 1 | False | 0 | 1.0 |
| 2 | False | 0 | 2.0 |
| 3 | False | 0 | 3.0 |
| 4 | False | 0 | 4.0 |
| ... | ... | ... | ... |
| 56 | False | 0 | 56.0 |
| 57 | False | 0 | 57.0 |
| 58 | False | 0 | 58.0 |
| 59 | False | 0 | 59.0 |
| 60 | False | 0 | 60.0 |
61 rows × 3 columns
#| echo: true
mdlhist_fault.faulty.fxns.detect_hazard
aa.active_actions: array(61)
aa.flows.hazard.s.present: array(61)
aa.flows.hazard.s.percieved: array(61)
aa.flows.hazard.s.mitigated: array(61)
aa.flows.outcome.s.num_perceptions: array(61)
aa.flows.outcome.s.num_actions: array(61)
aa.acts.perceive.m.mode: array(61)
aa.acts.perceive.m.faults.failed: array(61)
aa.acts.perceive.m.faults.unable: array(61)
aa.acts.perceive.m.sub_faults: array(61)
aa.acts.act.m.mode: array(61)
aa.acts.act.m.faults.failed: array(61)
aa.acts.act.m.faults.unable: array(61)
aa.acts.act.m.sub_faults: array(61)
m.sub_faults: array(61)
#| echo: true
mdl.fxns['detect_hazard'].h
aa.active_actions: array(61)
aa.flows.hazard.s.present: array(61)
aa.flows.hazard.s.percieved: array(61)
aa.flows.hazard.s.mitigated: array(61)
aa.flows.outcome.s.num_perceptions: array(61)
aa.flows.outcome.s.num_actions: array(61)
aa.acts.perceive.m.mode: array(61)
aa.acts.perceive.m.faults.failed: array(61)
aa.acts.perceive.m.faults.unable: array(61)
aa.acts.perceive.m.sub_faults: array(61)
aa.acts.act.m.mode: array(61)
aa.acts.act.m.faults.failed: array(61)
aa.acts.act.m.faults.unable: array(61)
aa.acts.act.m.sub_faults: array(61)
m.sub_faults: array(61)
We can further use draw_from to view the state of the ASG. See below
#| echo: true
result_fault.get_faulty().tend.graph.draw_from(4, mdlhist_fault)
(<Figure size 1200x1000 with 1 Axes>, <Axes: title={'center': ' t=4'}>)
Note the lack of a fault at this time-step, despite it being instantiated here. This is because the fault was removed at the end of the same time-step it was added in.
The ‘unable’ fault, on the other hand, stays throughout the simulation and thus shows up
#| echo: true
endstate_unable, mdlhist_unable = prop.one_fault(mdl, 'detect_hazard.aa.acts.act','unable', time=4,
to_return={'graph','graph.fxns.detect_hazard.aa'})
fig, axs = mdlhist_unable.plot_line('fxns.pass_hazard.s.hazards_mitigated',
'fxns.pass_hazard.s.hazards_propagated',
'flows.hazard.s.present',
'flows.hazard.s.percieved',
'flows.hazard.s.mitigated',
'fxns.detect_hazard.aa.flows.hazard.s.percieved',
'fxns.detect_hazard.aa.acts.act.m.faults.unable',
figsize=(10, 5), time_slice=[4])
#| echo: true
mdlhist_unable.faulty.fxns.detect_hazard.aa.active_actions
array([{'perceive'}, {'perceive'}, {'perceive'}, {'perceive'}, {'done'},
{'perceive'}, {'perceive'}, {'perceive'}, {'done'}, {'perceive'},
{'perceive'}, {'perceive'}, {'done'}, {'perceive'}, {'perceive'},
{'perceive'}, {'done'}, {'perceive'}, {'perceive'}, {'perceive'},
{'done'}, {'perceive'}, {'perceive'}, {'perceive'}, {'done'},
{'perceive'}, {'perceive'}, {'perceive'}, {'done'}, {'perceive'},
{'perceive'}, {'perceive'}, {'done'}, {'perceive'}, {'perceive'},
{'perceive'}, {'done'}, {'perceive'}, {'perceive'}, {'perceive'},
{'done'}, {'perceive'}, {'perceive'}, {'perceive'}, {'done'},
{'perceive'}, {'perceive'}, {'perceive'}, {'done'}, {'perceive'},
{'perceive'}, {'perceive'}, {'done'}, {'perceive'}, {'perceive'},
{'perceive'}, {'done'}, {'perceive'}, {'perceive'}, {'perceive'},
{'done'}], dtype=object)
#| echo: true
mdlhist_unable.faulty.fxns.detect_hazard.aa.acts.act.m.faults.unable
array([False, False, False, False, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True])
#| echo: true
mdl.fxns['detect_hazard'].h.aa.acts.perceive
m.mode: array(61)
m.faults.failed: array(61)
m.faults.unable: array(61)
m.sub_faults: array(61)
#| echo: true
mdl.fxns['detect_hazard'].aa.create_hist([0.0])
active_actions: array(1)
flows.hazard.s.present: array(1)
flows.hazard.s.percieved: array(1)
flows.hazard.s.mitigated: array(1)
flows.outcome.s.num_perceptions: array(1)
flows.outcome.s.num_actions: array(1)
acts.perceive.m.mode: array(1)
acts.perceive.m.faults.failed: array(1)
acts.perceive.m.faults.unable: array(1)
acts.perceive.m.sub_faults: array(1)
acts.act.m.mode: array(1)
acts.act.m.faults.failed: array(1)
acts.act.m.faults.unable: array(1)
acts.act.m.sub_faults: array(1)
#| echo: true
mdl.fxns['detect_hazard'].aa.track
['acts', 'flows', 'active_actions', 'i']
#| echo: true
mdl.fxns['detect_hazard'].aa.h.acts.perceive.m.faults.failed
array([False])
#| echo: true
mdlhist_unable.nominal.fxns.detect_hazard.aa.acts
perceive.m.mode: array(61)
perceive.m.faults.failed: array(61)
perceive.m.faults.unable: array(61)
perceive.m.sub_faults: array(61)
act.m.mode: array(61)
act.m.faults.failed: array(61)
act.m.faults.unable: array(61)
act.m.sub_faults: array(61)
#| echo: true
endstate_unable.get_faulty().tend.graph.draw_from(4, mdlhist_unable)
(<Figure size 1200x1000 with 1 Axes>, <Axes: title={'center': ' t=4'}>)
#| echo: true
fig, ax = endstate_unable.get_faulty().tend['graph.fxns.detect_hazard.aa'].draw_from(0, mdlhist_unable)
#| echo: true
fig, ax = endstate_unable.get_faulty().tend['graph.fxns.detect_hazard.aa'].draw_from(5, mdlhist_unable, rem_ind=0)