Statestep Python Template

Version: 1.1

Notes

At the time of release, the earliest version of Python against which this template had been tested was Python 2.7.13

Before using this template, first read the general instructions on how to use FMPP for code generation. Note also that more up-to-date tips or information may have been added to the Statestep website since this template was released.

Using the Template

Some example Python code is included in the package, showing how the generated code can be used to provide decision table or state machine functionality. For state machine models, you can start by trying out 'fsm.py', either with the model 'harelsWatch.s2' that comes with Statestep or a model of your own. First, run the model through the template in the usual way. For example, assuming the file 'harelsWatch.s2' and the template are both in the working directory:

fmpp model.py.ftl -o harelsWatch.py -D "doc:xml(harelsWatch.s2, {namespaceAware : false})"

Then simply run 'fsm.py', passing the generated code as an argument:

python fsm.py harelsWatch.py

(This assumes the Python executable is on your path; you may need to type something like 'C:\python24\python' instead of 'python' above.)

For a decision table, there are several possible approaches. The code in 'decision.py' is designed to exercise a decision table in which one or more variables may have been used to specify the outcome, such as the 'activitiesOutcomes' model included with Statestep. You can use it in a similar way to 'fsm.py' above. For example, assuming code generated from the model 'activitiesOutcomes.s2' has been written to the file 'activitiesOutcomes.py':

python decision.py activitiesOutcomes.py

Another possible approach is to encode the outcomes in the names of the rules. For example, if the outcome of your decision table was 'pass' or 'fail' then, in the model, you could give your rules names like 'pass__rule1', 'pass__rule2', 'fail__rule3', and so on. Assuming the generated code is in 'model.py' and a single event called 'decide', your own code you might then look like:

import model
...
rule, dummy = model.applyRules('decide', combination)
if rule:
   outcome = rule.split('__')[0]
   ...

Still another possibility is simply to describe the outcome as comment text in the formula field of the rules (as is done in the 'activities.s2' sample model provided with Statestep). If you want to take this approach, you will find template code to extract rule formula text in the file 'ruleformulas.py.ftl'; add this to the main 'model.py.ftl' before processing that with FMPP.

Advanced Use: Embedding Code in the Model

For some applications, it may be convenient to incorporate executable code directly within the rules of the model. This is not particularly recommended but is sometimes a useful option to have available. The suggested way of embedding code in rules is to precede each line of code with '#:', for example:

Event 'decide', Rule 'B':

Temperature Weather Wind
cold
warm
fine
rain
calm
     
# go hillwalking
#:print 'Take a hike!'

As far as Statestep is concerned, the print statement here is a comment just like any other; by starting comment lines which contain Python code with '#:' instead of '#' we are just making it easier to extract the embedded code. To extract this code as part of the code generation step, add the contents of the file 'extralogic.py.ftl' to 'model.py.ftl' before processing it with FMPP. The resulting code will then have an additional function:

def extralogic(event, rule, current, next):
   if event == 'decide' and rule == 'B':
      print 'Take a hike!'
   elif ... 
      ...
   return next

which, in your own code, you can pass to applyRules:

rule, outcome = model.applyRules(event, comb, model.extralogic)

The way 'extra logic' works means you also have the option of embedding code which helps to decide what the outcome of a rule is, for example:

Event 'decide', Rule 'A':

Temperature Weather Wind Activity
hot
warm
fine
rain
breeze  
      hillwalk
windsurf
# If there are enough boards for everyone then
# go windsurfing; otherwise go hillwalking.

#:import extras
#:if len(extras.party) <= extras.num_boards:
#:   next = next.copy({'Activity':'windsurf'})
#:else:
#:   next = next.copy({'Activity':'hillwalk'})

— where party (a list of people's names) and num_boards are variables defined in a module extras in your own code. Of course, it would be better to have a single import extras at the start of your .ftl template file instead of importing it within the code for individual rules like this; for the remaining examples, we will assume the object extras is already available.

By including a 'return' statement in the code, you can even decide whether a rule applies at all:

Event 'decide', Rule 'A_1':

Temperature Weather Wind Activity
hot
warm
fine
rain
breeze  
      hillwalk
# There are more people than windsurfing boards.

#:if not len(extras.party) > extras.num_boards:
#:   # this rule does not apply
#:   return False

Event 'decide', Rule 'A_2':

Temperature Weather Wind Activity
hot
warm
fine
rain
breeze  
      windsurf
# We have enough windsurfing boards for everyone.

#:if len(extras.party) > extras.num_boards:
#:   # this rule does not apply
#:   return None

This works because whenever extralogic returns None, applyRules looks for another rule instead.

Note that the values of the variables are also passed to extralogic and are available in the argument current if needed. Thus, you could also embed code like:

#:if current['Weather'] == 'fine' and \
#:current['Temperature'] == 'hot':
#:   print "Take some sunscreen."

Finally, remember that you can always write your own 'extra logic' function separately, that is, you don't have to embed code in the model to make use of this callback mechanism.

Changes in version 1.1