Refactor Charts Programmatically
You can use the Stateflow® API to programmatically refactor common issues that affect the legibility of Stateflow charts. You can programmatically analyze a chart to identify issues with confusingly nested states, inconsistent state names, or extraneous transitions, and then fix these issues by using the Stateflow API.
Open the Model
In this example, you use a poorly designed model that emulates the behavior of an insect. The chart describes this behavior:
The insect is confined to a box.
The insect moves in a straight line by default.
When the insect hits a wall, the insect bounces off the wall and continues in the opposite direction.
When the insect sees prey, the insect redirects its trajectory to head towards the prey at an increased speed.
When the insect sees a predator, the insect redirects its trajectory to run away from the predator at an increased speed.
The insect can see in a directional cone of finite length, which means the insect must be close to an object and facing it in order to detect its presence.
After 12 hours, the insect is tired and has to rest.
This Stateflow chart has an inconsistent naming scheme in which some state names start with a capital letter and some state names do not. Additionally, the chart contains extraneous transitions and states that are nested more than three levels deep.
Chart Depth Issues
To interact with the Stateflow chart by using the API, use the find
function to access the
Stateflow.Chart
object for the chart.
ch = find(sfroot,"-isa","Stateflow.Chart",Name="Behavioral Logic");
Flagging Chart Depth Issues
To improve chart legibility, avoid nesting charts deeper than three levels
deep, where possible. Instead, use subcharts. You can search for states that are
nested too deeply by using find
and its optional
arguments to create an array:
all_states = find(ch,"-isa","Stateflow.State"); shallow_states = find(ch,"-isa","Stateflow.State","-depth",3); deep_states = setdiff(all_states,shallow_states);
You can inspect the contents of that array in MATLAB through several means. For example, you can write a script to print the contents of the array to the Command Window. This script includes information regarding the hierarchy, which can help you find specific states easily in a large or complex Stateflow chart.
num_deep_states = length(deep_states); deep_state_names = ""; for i=1:num_deep_states deep_path_text = getHierarchy(deep_states(i)); deep_state_names = deep_state_names + newline + " * " + deep_path_text; end warning("The following states have been flagged for having a depth greater than 3." + ... newline + "Consider creating subcharts instead." + deep_state_names)
The for
loop in this script iterates through each problem
state. The getHierarchy
helper function, shown below,
traces the parentage of the state to the original model, which shows you how to
navigate to the flagged state. The script calls getParent
in a loop to trace the parents of the state as far back as the model. The
warning
function prints the
paths to all the problem states in the Command Window.
function path_text = getHierarchy(state) path_text = state.Path + "." + state.Name; indices = strfind(path_text,"/"); for i = 3:numel(indices) path_text = replaceBetween(path_text,indices(i),indices(i),"."); end end
For the insect behavior example, this script flags the substates under searching. These substates are four levels deep.
Warning: The following states have been flagged for having a depth greater than 3. Consider creating subcharts instead. * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.normal * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.yBump * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.xBump
Fixing Chart Depth Issues
After you identify issues with the depth of the states in your chart, you can fix these issues programmatically.
To convert a Stateflow.State
to a subchart,
access the IsSubchart property and set it to true. To automate this process
for a large-scale chart, the helper function createSubchart
looks for states at a depth of three which contain other nested states, then
converts the states at a depth of three to subcharts. Then, it repeats the
process by calling itself recursively with the new subcharts as parents.
function createSubchart(parent,depth) states = setdiff( ... find(parent,"-isa","Stateflow.State","-depth",depth), ... find(parent,"-isa","Stateflow.State","-depth",depth-1)); for i=1:length(states) children = setdiff(find(states(i),'-isa','Stateflow.State', ... '-depth',1),states(i)); for j=1:length(children) states(i).isSubchart = true; createSubchart(states(i),depth); break end end end
State Name Issues
Flagging State Name Issues
To improve readability, use the same naming conventions throughout the chart.
You can use the find
function to identify
the states that do not start with capital letters.
correct_states = find(ch,"-isa","Stateflow.State", ... "-regexp","Name","^[A-Z]\w*"); misnamed_states = setdiff(all_states,correct_states);
When using find
to search, techniques
you can use include:
Logical expressions such as
and
to add several conditions for your search.Regular expressions to search for open-ended combinations of alphanumeric characters. In this example,
regexp
searches for states whose names start with an uppercase letter and are composed entirely of alphanumeric characters or underscores.
You can view the results in the misnamed_states
variable or
use a script to print the results to the Command Window. For this example, use
this script:
bad_state_names = ""; for j=1:length(misnamed_states) misnamed_path_text = getHierarchy(misnamed_states(j)); bad_state_names = bad_state_names + newline + " * " + misnamed_path_text; end warning("The following states have been flagged for an improper naming scheme." + ... newline + "Consider renaming these states to start with a capital letter." + ... bad_state_names)
The script outputs the states that start with lowercase letters:
Warning: The following states have been flagged for an improper naming scheme. Consider renaming these states to start with a capital letter. * insect_behavior_example/Behavioral Logic/Awake.Danger.yBump * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.normal * insect_behavior_example/Behavioral Logic/Awake.Safe.searching * insect_behavior_example/Behavioral Logic/Awake.Danger.xBump * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.yBump * insect_behavior_example/Behavioral Logic/Awake.Safe.searching.xBump
Fixing State Name Issues
After identifying the list of state names, replace the first letter of each
identified state in misnamed_states
with its uppercase
equivalent by using upper
.
for k=1:length(misnamed_states) state = misnamed_states(k); state.Name = [upper(state.Name(1)) state.Name(2:end)]; end
Note
Similarly, you can refactor chart data names with the following code:
rt = sfroot; rt.find('-isa', 'Stateflow.Data') data.refactor('newName')
Unnecessary Transitions
Flagging Unnecessary Transitions
Unnecessary transitions may increase the complexity of your charts. For
example, transitions may not be necessary if you have several sequential
transitions. In this example, you can define an unnecessary transition as
connected to a junction or chain of junctions with only one sourced transition
and one sunk transition. This code uses functions sourcedTransitions
and sinkedTransitions
to
identify these transitions.
function is_linear = checkLinearity(junction) is_linear = false; source_transitions = sourcedTransitions(junction); sink_transitions = sinkedTransitions(junction); if (length(source_transitions)<=1) && (length(sink_transitions)<=1) is_linear = true; end end
You can view the results in the is_linear
variable.
Alternatively, you can use a script to print the results to the Command Window.
This code uses the names of the parents to which these transitions belong.
Because most transitions do not have a unique name, the code prints the name of
the parent state, box, or subchart.
junctions = find(ch,"-isa","Stateflow.Junction"); bad_transition_parent_names = ""; flagged_junctions = []; for k=1:length(junctions) if checkLinearity(junctions(k)) flagged_junctions = [flagged_junctions junctions(k)]; bad_transition_parent_names = bad_transition_parent_names + ... newline + " * " + getHierarchy(getParent(junctions(k))); end end warning("The following states contain transitions that have been flagged for" + ... newline + "superfluousness. You may be able to combine transitions within these states into" + ... newline + "one transition with several actions." + ... bad_transition_parent_names)
The script prints the names of the transitions with only one sourced transition and one sunk transition:
Warning: The following states contain transitions that have been flagged for superfluousness. You may be able to combine transitions within these states into one transition with several actions. * insect_behavior_example/Behavioral Logic/Awake.Danger * insect_behavior_example/Behavioral Logic/Awake.Danger * insect_behavior_example/Behavioral Logic/Awake.Danger
Fixing Unnecessary Transitions
In this example, the variable flagged_junctions
contains
the list of transitions with only one sourced transition and one sunk
transition. This image shows some of the identified transitions.
This script deletes unnecessary transitions in the chart and replaces them with a single transition. The script:
Locates the start transition and end transition of each chain of one or more unnecessary transitions using the helper function
findEnds
, which uses theStateflow.Transition
API object properties to navigate through the chart programmatically. It uses Source and Destination to move in opposite directions over the chain. Its sole input is theflagged_junctions
array.Uses the outputs of
findEnds
to navigate the chain from the start transition until it reaches the end transition.Stores any guards or actions from the intermediate transitions in variables
cat_guard
andcat_action
using the helper functionbuildConcatLabelStr
. To see more about the formatting involved in constructing a label string, see Specify Labels in States and Transitions Programmatically.Deletes the intermediate transitions and junctions.
Builds a new, concatenated label from
cat_guard
andcat_action
and applies it to the start transition.Redirects the destination of the start transition to the terminus of the end transition. The start transition now encompasses the length of the original chain.
[starts,ends] = findEnds(flagged_junctions); for n=1:length(starts) current = starts(n); cat_guard = ""; cat_action = ""; while current ~= ends(n) [current,cat_guard,cat_action] = buildConcatLabelStr(current,... cat_guard,cat_action); previous = current; current = sourcedTransitions(current.Destination); delete(previous.Destination); if ~ismember(previous,starts) delete(previous); end end [~,cat_guard,cat_action] = buildConcatLabelStr(current,cat_guard,... cat_action); temp_string = ""; if strlength(cat_guard) temp_string = "[" + cat_guard + "]"; end if strlength(cat_action) temp_string = temp_string + "{" + cat_action + "}"; end starts(n).LabelString = temp_string; starts(n).Destination = ends(n).Destination; delete(ends(n)); end function [starts,ends]=findEnds(flagged_junctions) starts = []; ends = []; while ~isempty(flagged_junctions) junction = flagged_junctions(1); flagged_junctions = flagged_junctions(2:end); [startPoint,flagged_junctions_1] = findEndPoint(... sinkedTransitions(junction),flagged_junctions,-1); [endPoint,flagged_junctions_2] = findEndPoint(... sourcedTransitions(junction),flagged_junctions,1); starts = [starts startPoint]; ends = [ends endPoint]; flagged_junctions = intersect(flagged_junctions_1,flagged_junctions_2); end function [start_trans,flagged_junc] = findEndPoint(transition,flagged_junc,dir) if dir == 1 next = transition.Destination; nextTrans = sourcedTransitions(next); else next = transition.Source; nextTrans = sinkedTransitions(next); end if isa(next,'Stateflow.Junction') && checkLinearity(next) flagged_junc = flagged_junc(~ismember(flagged_junc,next)); [start_trans,flagged_junc] = findEndPoint(nextTrans,flagged_junc,dir); else start_trans = transition; end end end function [current,cat_guard,cat_action] = buildConcatLabelStr( ... current,cat_guard,cat_action) guard = current.Condition; action = current.ConditionAction; if strlength(guard) if ~strlength(cat_guard) cat_guard = guard; else cat_guard = cat_guard + "&&" + guard; end end if strlength(action) if ~strlength(cat_action) cat_action = action; else cat_action = cat_action + ";" + action; end end end
After the code completes, the resulting chart contains no junctions with only one sourced transition and one sunk transition.