Skip to content

PrettyPFA

PFA was designed for data processing.

PrettyPFA provides a C-like syntax for PFA, with slight difference in syntax from mainstream progrmming languages.

A PrettyPFA document is split into sections, each of which has different rules. The syntax of these sections resembles PFA in YAML.

Not everything is built algorithmically in PFA; some parts of a PFA document, such as pre- and post-processing, are usually written by hand. For these parts, there are compilers that turn human-readable code into PFA.

Below are some examples of building models using PrettyPFA.

In [1]:
from titus.genpy import PFAEngine
from titus import prettypfa

Finding Square Root

In [2]:
pfaDocument = prettypfa.jsonNode("""
name: SquareRoot
input: double
output: union(double, null)
action:
  if (input >= 0.0)
    m.sqrt(input)
  else
    null
""")
engine, = PFAEngine.fromJson(pfaDocument)
In [3]:
engine.action(5)
Out[3]:
2.23606797749979

Rule Based Classification

JSON

In [4]:
pfaDocument = """
{
    "input": {
        "type": "record",
        "name": "Iris",
        "fields": [
          {"name": "sepal_length_cm", "type": "double"},
          {"name": "sepal_width_cm", "type": "double"},
          {"name": "petal_length_cm", "type": "double"},
          {"name": "petal_width_cm", "type": "double"},
          {"name": "class", "type": "string"}
        ]
    },
    "output": "string",
    "action": [{
        "if": {"<": ["input.petal_length_cm", 2.5]},
        "then": {"string": "Iris-setosa"},
        "else":{
            "if": {"<": ["input.petal_length_cm", 4.8]},
            "then": {"string": "Iris-versicolor"},
            "else":{
                "if": {"<": ["input.petal_width_cm", 1.7]},
                "then": {"string": "Iris-versicolor"},
                "else": {"string": "Iris-virginica"}
            }
        }
    }]
}
"""
engine, = PFAEngine.fromJson(pfaDocument)
In [5]:
engine.action({"sepal_length_cm": 5.1, "sepal_width_cm": 3.5,
               "petal_length_cm": 1.4, "petal_width_cm": 0.2,
               "class": "Iris-setosa"})
Out[5]:
'Iris-setosa'
In [6]:
import csv

dataset = csv.reader(open("../../assets/iris.csv"))
fields = next(dataset)

numCorrect = 0.0
numTotal = 0.0
for datum in dataset:
    asRecord = dict(zip(fields, datum))
    if engine.action(asRecord) == asRecord["class"]:
        numCorrect += 1.0
    numTotal += 1.0

print("accuracy", numCorrect/numTotal)
accuracy 0.9533333333333334

PrettyPFA (Conditional Statements)

In [7]:
pfaDocument = prettypfa.jsonNode('''
input: record(sepal_length_cm: double,
              sepal_width_cm: double,
              petal_length_cm: double,
              petal_width_cm: double)
output: string
action:
  if (input.petal_length_cm < 2.5)
    "Iris-setosa"
  else if (input.petal_length_cm < 4.8)
    "Iris-versicolor"
  else if (input.petal_width_cm < 1.7)
    "Iris-versicolor"
  else
    "Iris-virginica"
''')
engine, = PFAEngine.fromJson(pfaDocument)
In [8]:
engine.action({"sepal_length_cm": 5.1, "sepal_width_cm": 3.5,
               "petal_length_cm": 1.4, "petal_width_cm": 0.2,
               "class": "Iris-setosa"})
Out[8]:
'Iris-setosa'

PrettyPFA (Rule Based)

In [9]:
pfaDocument = prettypfa.jsonNode('''
input: record(sepal_length_cm: double,
              sepal_width_cm: double,
              petal_length_cm: double,
              petal_width_cm: double)
output: string
types:
  Rules = array(record(field: string,
                       cut: double,
                       result: string))
cells:
  rules(Rules) = [
    {field: "petal_length_cm", cut: 2.5, result: "Iris-setosa"},
    {field: "petal_length_cm", cut: 4.8, result: "Iris-versicolor"},
    {field: "petal_width_cm", cut: 1.7, result: "Iris-versicolor"},
    {field: "none", cut: -1, result: "Iris-virginica"}
  ]

action:
  var result = "";

  for (index = 0; result == ""; index = index + 1) {
    var rule = rules[index];

    var fieldValue =
      if (rule.field == "sepal_length_cm") input.sepal_length_cm
      else if (rule.field == "sepal_width_cm") input.sepal_width_cm
      else if (rule.field == "petal_length_cm") input.petal_length_cm
      else if (rule.field == "petal_width_cm") input.petal_width_cm
      else -1.0;

    if (rule.field == "none"  ||  fieldValue < rule.cut)
      result = rule.result;
  };

  result
''')
engine, = PFAEngine.fromJson(pfaDocument)
In [10]:
engine.action({"sepal_length_cm": 5.1, "sepal_width_cm": 3.5,
               "petal_length_cm": 1.4, "petal_width_cm": 0.2,
               "class": "Iris-setosa"})
Out[10]:
'Iris-setosa'

Quadratic Formula

In [11]:
pfaDocument = prettypfa.json('''
input: record(a: double, b: double, c: double)
output: union(null,
              record(Output,
                     solution1: double,
                     solution2: double))
action:
  var a = input.a, b = input.b, c = input.c;

  var discriminant = b**2 - 4*a*c;
  if (discriminant >= 0.0) {
    // if there are any real solutions, return them
    var x1 = -b + m.sqrt(discriminant)/(2*a);
    var x2 = -b - m.sqrt(discriminant)/(2*a);
    new(Output, solution1: x1, solution2: x2)
  }
  else
    // otherwise, return null (N/A)        
    null
''')
engine, = PFAEngine.fromJson(pfaDocument)
In [12]:
print(engine.action({"a": 1, "b": 8, "c": 4}))
{'solution1': -4.535898384862246, 'solution2': -11.464101615137753}
In [13]:
print(engine.action({"a": 1, "b": 2, "c": 3}))
None

Applying Function

In [14]:
pfa = prettypfa.json("""
input: enum([linear, square, cube])
output: int
action:
  apply(input, 2)
fcns:
  linear = fcn(x: int -> int) x;
  square = fcn(x: int -> int) x**2;
  cube = fcn(x: int -> int) x**3;
""")
engine, = PFAEngine.fromJson(pfa)
In [15]:
engine.action("cube")
Out[15]:
8