Parse output

A parser can be any program that analyzes files in the run’s directory (e.g. run.log) and manipulates the properties file in the same directory.

To make parsing easier, however, you can use the Parser class. Here is an example parser for the FF planner:

../examples/ff/ff-parser.py
#! /usr/bin/env python

"""
FF example output:

[...]

ff: found legal plan as follows

step    0: UP F0 F1
        1: BOARD F1 P0
        2: DOWN F1 F0
        3: DEPART F0 P0


time spent:    0.00 seconds instantiating 4 easy, 0 hard action templates
               0.00 seconds reachability analysis, yielding 4 facts and 4 actions
               0.00 seconds creating final representation with 4 relevant facts
               0.00 seconds building connectivity graph
               0.00 seconds searching, evaluating 5 states, to a max depth of 2
               0.00 seconds total time
"""

import re

from lab.parser import Parser


def error(content, props):
    if props["planner_exit_code"] == 0:
        props["error"] = "plan-found"
    else:
        props["error"] = "unsolvable-or-error"


def coverage(content, props):
    props["coverage"] = int(props["planner_exit_code"] == 0)


def get_plan(content, props):
    # All patterns are parsed before functions are called.
    if props.get("evaluations") is not None:
        props["plan"] = re.findall(r"^(?:step)?\s*\d+: (.+)$", content, re.M)


def get_times(content, props):
    props["times"] = re.findall(r"(\d+\.\d+) seconds", content)


def trivially_unsolvable(content, props):
    props["trivially_unsolvable"] = int(
        "ff: goal can be simplified to FALSE. No plan will solve it" in content
    )


parser = Parser()
parser.add_pattern("node", r"node: (.+)\n", type=str, file="driver.log", required=True)
parser.add_pattern(
    "planner_exit_code", r"run-planner exit code: (.+)\n", type=int, file="driver.log"
)
parser.add_pattern("evaluations", r"evaluating (\d+) states")
parser.add_function(error)
parser.add_function(coverage)
parser.add_function(get_plan)
parser.add_function(get_times)
parser.add_function(trivially_unsolvable)
parser.parse()

You can add this parser to all runs by using add_parser():

>>> from pathlib import Path
>>> from lab.experiment import Experiment
>>> exp = Experiment()
>>> # The path can be absolute or relative to the working directory at build time.
>>> parser = Path(__file__).resolve().parents[1] / "examples/ff/ff-parser.py"
>>> exp.add_parser(parser)

All added parsers will be run in the order in which they were added after executing the run’s commands.

If you need to change your parsers and execute them again, use the add_parse_again_step() method to re-parse your results.