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()
../../_images/eb452de3b3082babb42d77ec7365031e365c54b058b474b3bbba450386d36f74.png

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: >)
../../_images/78da933e8ccdf268df41ef5da0550ccce651bde21d231c0d1c8e14fa8477a8ba.png
#| 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()
../../_images/eb452de3b3082babb42d77ec7365031e365c54b058b474b3bbba450386d36f74.png

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))
../../_images/1ca299177e3d1c6a2b9e15c32ca5b6aad2b52172da9be9f347335933d5d2a5d7.png
#| 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))
../../_images/5395bb43266f60d67e8b94beeaf0505a6ab95691e80fbbafa6ad1ed2164c49d6.png

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])
../../_images/82f59184947ac28d9f0d2e91accfdd508aaeaed131bb64d2d42dc26f5dd94934.png

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()
../../_images/24f779deabba3d0a3f9a6a824ff67bc6fd8dfa977470eb35a4e6cd06b3ac781e.png

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'}>)
../../_images/7ad536db7447e4626dc3c1be68a24993ab44e58a950ed308f8ffdbcb7e76b824.png

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])
../../_images/c9bd46cbc75159bad197abf737c2b21176fbd1da83213d01bdfb4893a3424d1f.png
#| 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'}>)
../../_images/5ff50705c007b3d037b8df6f950fab89866358bdc4e9f181d37adf6a9d33a489.png
#| echo: true
fig, ax = endstate_unable.get_faulty().tend['graph.fxns.detect_hazard.aa'].draw_from(0, mdlhist_unable)
../../_images/0af11a649c463441b37f4b345158fef0c7a80bc659ac840d28383e6c57e59af3.png
#| echo: true
fig, ax = endstate_unable.get_faulty().tend['graph.fxns.detect_hazard.aa'].draw_from(5, mdlhist_unable, rem_ind=0)
../../_images/25cf42c7ff158fd7fe9efd6caa87570d6d1a877d944a3d2d94627c3ead8e9935.png