PyReason Output

This section outline four functions that help display and explain the PyReason output and reasoning process.

Filter and Sort Nodes

This function filters and sorts the node changes in the interpretation and returns as a list of Pandas dataframes that contain the filtered and sorted interpretations that are easy to access.

Basic Tutorial Example

To see filter_and_sort_nodes in action we will look at the example usage in PyReasons Basic Tutorial.

Note

Find the full, explained tutorial here here

The tutorial take in a basic graph of people and their pets, then adds a Rule and a Fact.

pr.load_graph(g)
pr.add_rule(pr.Rule('popular(x) <-1 popular(y), Friends(x,y), owns(y,z), owns(x,z)', 'popular_rule'))
pr.add_fact(pr.Fact('popular(Mary)', 'popular_fact', 0, 2))

# Run the program for two timesteps to see the diffusion take place
faulthandler.enable()
interpretation = pr.reason(timesteps=2)

We add the filter_and_sort_nodes after the interpretation is run, before PyReason prints the output:

# Display the changes in the interpretation for each timestep
dataframes = pr.filter_and_sort_nodes(interpretation, ['popular'])
for t, df in enumerate(dataframes):
    print(f'TIMESTEP - {t}')
    print(df)
    print()

This will print the nodes at each timestep in the reasoning process.

Expected Output

Using filter_and_sort_nodes, the expected output is :

 TIMESTEP - 0
  component    popular
0      Mary  [1.0,1.0]


 TIMESTEP - 1
  component    popular
0      Mary  [1.0,1.0]
1    Justin  [1.0,1.0]


 TIMESTEP - 2
  component    popular
0      Mary  [1.0,1.0]
1    Justin  [1.0,1.0]
2      John  [1.0,1.0]

Filter and Sort Edges

This function filters and sorts the edge changes in the interpretation and returns a list of Pandas dataframes that contain the filtered and sorted interpretations, making them easy to access.

Infer Edges Example

To see filter_and_sort_edges in action, we will look at the example usage in PyReason’s Infer Edges Tutorial.

Note

Find the full, explained tutorial here here.

The tutorial takes in a basic graph of airports and connections, then infers an edges between two unconnected airports.

pr.load_graph(G)
pr.add_rule(pr.Rule('isConnectedTo(A, Y) <-1  isConnectedTo(Y, B), Amsterdam_Airport_Schiphol(B), Vnukovo_International_Airport(A)', 'connected_rule_1', infer_edges=True))

# Run the program for two timesteps to see the diffusion take place
interpretation = pr.reason(timesteps=1)

We add the filter_and_sort_edges function after the interpretation is run, before PyReason prints the output:

# Display the changes in the interpretation for each timestep
dataframes = pr.filter_and_sort_edges(interpretation, ['isConnectedTo'])
for t, df in enumerate(dataframes):
    print(f'TIMESTEP - {t}')
    print(df)
    print()

This will print the edges at each timestep in the reasoning process.

Expected Output

Using filter_and_sort_edges, the expected output is:

Timestep: 0
Timestep: 1

Converged at time: 1
Fixed Point iterations: 2
TIMESTEP - 0
                                        component isConnectedTo
0                 (Amsterdam_Airport_Schiphol, Yali)    [1.0, 1.0]
1  (Riga_International_Airport, Amsterdam_Airport...    [1.0, 1.0]
2   (Riga_International_Airport, Düsseldorf_Airport)    [1.0, 1.0]
3  (Chișinău_International_Airport, Riga_Internat...    [1.0, 1.0]
4            (Düsseldorf_Airport, Dubrovnik_Airport)    [1.0, 1.0]
5  (Pobedilovo_Airport, Vnukovo_International_Air...    [1.0, 1.0]
6  (Dubrovnik_Airport, Athens_International_Airport)    [1.0, 1.0]
7  (Vnukovo_International_Airport, Hévíz-Balaton_...    [1.0, 1.0]

TIMESTEP - 1
                                        component isConnectedTo
0  (Vnukovo_International_Airport, Riga_Internati...    [1.0, 1.0]

Get Rule Trace

This function returns the trace of the program as 2 pandas dataframes (one for nodes, one for edges). This includes every change that has occurred to the interpretation. If atom_trace was set to true this gives us full explainability of why interpretations changed

Advanced Tutorial Example

To see get_rule_trace in action we will look at the example usage in PyReasons Advanced Tutorial.

The tutorial takes in a graph of we have customers, cars, pets and their relationships. We first have customer_details followed by car_details , pet_details , travel_details.

We will only add the get_rule_trace function after the interpretation:

interpretation = pr.reason(timesteps=5)
nodes_trace, edges_trace = pr.get_rule_trace(interpretation)

Expected Output

Using get_rule_trace, the expected output of nodes_trace and edges_trace is:

Click here for the full table.

Nodes Trace:

    Time  Fixed-Point-Operation         Node  ...      Occurred Due To               Clause-1      Clause-2
0      0                      0  popular-fac  ...  popular(customer_0)                   None          None
1      1                      2  popular-fac  ...  popular(customer_0)                   None          None
2      1                      2   customer_4  ...        cool_car_rule  [(customer_4, Car_4)]       [Car_4]
3      1                      2   customer_6  ...        cool_car_rule  [(customer_6, Car_4)]       [Car_4]
4      1                      2   customer_3  ...        cool_pet_rule  [(customer_3, Pet_2)]       [Pet_2]
5      1                      2   customer_4  ...        cool_pet_rule  [(customer_4, Pet_2)]       [Pet_2]
6      1                      3   customer_4  ...          trendy_rule           [customer_4]  [customer_4]
7      2                      4  popular-fac  ...  popular(customer_0)                   None          None
8      2                      4   customer_4  ...        cool_car_rule  [(customer_4, Car_4)]       [Car_4]
9      2                      4   customer_6  ...        cool_car_rule  [(customer_6, Car_4)]       [Car_4]
10     2                      4   customer_3  ...        cool_pet_rule  [(customer_3, Pet_2)]       [Pet_2]
11     2                      4   customer_4  ...        cool_pet_rule  [(customer_4, Pet_2)]       [Pet_2]
12     2                      5   customer_4  ...          trendy_rule           [customer_4]  [customer_4]
13     3                      6  popular-fac  ...  popular(customer_0)                   None          None
14     3                      6   customer_4  ...        cool_car_rule  [(customer_4, Car_4)]       [Car_4]
15     3                      6   customer_6  ...        cool_car_rule  [(customer_6, Car_4)]       [Car_4]
16     3                      6   customer_3  ...        cool_pet_rule  [(customer_3, Pet_2)]       [Pet_2]
17     3                      6   customer_4  ...        cool_pet_rule  [(customer_4, Pet_2)]       [Pet_2]
18     3                      7   customer_4  ...          trendy_rule           [customer_4]  [customer_4]
19     4                      8  popular-fac  ...  popular(customer_0)                   None          None
20     4                      8   customer_4  ...        cool_car_rule  [(customer_4, Car_4)]       [Car_4]
21     4                      8   customer_6  ...        cool_car_rule  [(customer_6, Car_4)]       [Car_4]
22     4                      8   customer_3  ...        cool_pet_rule  [(customer_3, Pet_2)]       [Pet_2]
23     4                      8   customer_4  ...        cool_pet_rule  [(customer_4, Pet_2)]       [Pet_2]
24     4                      9   customer_4  ...          trendy_rule           [customer_4]  [customer_4]
25     5                     10  popular-fac  ...  popular(customer_0)                   None          None
26     5                     10   customer_4  ...        cool_car_rule  [(customer_4, Car_4)]       [Car_4]
27     5                     10   customer_6  ...        cool_car_rule  [(customer_6, Car_4)]       [Car_4]
28     5                     10   customer_3  ...        cool_pet_rule  [(customer_3, Pet_2)]       [Pet_2]
29     5                     10   customer_4  ...        cool_pet_rule  [(customer_4, Pet_2)]       [Pet_2]
30     5                     11   customer_4  ...          trendy_rule           [customer_4]  [customer_4]

Edges Trace

Click here for the full table.

    Time  ...                                           Clause-2
0      0  ...         [(customer_1, Car_0), (customer_1, Car_8)]
1      0  ...         [(customer_1, Car_0), (customer_1, Car_8)]
2      0  ...  [(customer_2, Car_1), (customer_2, Car_3), (cu...
3      0  ...         [(customer_1, Car_0), (customer_1, Car_8)]
4      0  ...         [(customer_1, Car_0), (customer_1, Car_8)]
..   ...  ...                                                ...
61     5  ...         [(customer_0, Car_2), (customer_0, Car_7)]
62     5  ...         [(customer_5, Car_5), (customer_5, Car_2)]
63     5  ...  [(customer_3, Car_3), (customer_3, Car_0), (cu...
64     5  ...         [(customer_6, Car_6), (customer_6, Car_4)]
65     5  ...         [(customer_0, Car_2), (customer_0, Car_7)]

Save Rule Trace

This function saves the trace of the program as two pandas dataframes (one for nodes, one for edges). This includes every change that has occurred to the interpretation. If atom_trace was set to true, this provides full explainability of why interpretations changed.

Infer Edges Tutorial Example

To see save_rule_trace in action, we will look at an example usage in PyReason’s Infer Edges Tutorial.

Note

Find the full, explained tutorial here here.

This tutorial takes a graph with airports and their connections.

We will only add the save_rule_trace function after the interpretation:

interpretation = pr.reason(timesteps=1)
pr.save_rule_trace(interpretation, folder='./rule_trace_output')

Expected Output

Using save_rule_trace, the expected output is:

Saved Nodes Trace:

The nodes trace will be saved as a CSV file in the specified folder. It will contain the time, the fixed-point operation, the node, and the clause information that led to the change in each timestep. Here’s an example snippet of how the data will look when saved:

Click here for the full table.

Time,Fixed-Point-Operation,Node,Label,Old Bound,New Bound,Occurred Due To
0,0,Amsterdam_Airport_Schiphol,Amsterdam_Airport_Schiphol,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Riga_International_Airport,Riga_International_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Chișinău_International_Airport,Chișinău_International_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Yali,Yali,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Düsseldorf_Airport,Düsseldorf_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Pobedilovo_Airport,Pobedilovo_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Dubrovnik_Airport,Dubrovnik_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Hévíz-Balaton_Airport,Hévíz-Balaton_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Athens_International_Airport,Athens_International_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact
0,0,Vnukovo_International_Airport,Vnukovo_International_Airport,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact

Saved Edges Trace:

The edges trace will be saved as another CSV file. It will contain the time, the edge relationship changes, and the clauses that were involved. Here’s a snippet of how the edge trace will look when saved:

Click here for the full table.

Time,Fixed-Point-Operation,Edge,Label,Old Bound,New Bound,Occurred Due To,Clause-1,Clause-2,Clause-3
0,0,"('Amsterdam_Airport_Schiphol', 'Yali')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Riga_International_Airport', 'Amsterdam_Airport_Schiphol')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Riga_International_Airport', 'Düsseldorf_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Chișinău_International_Airport', 'Riga_International_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Düsseldorf_Airport', 'Dubrovnik_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Pobedilovo_Airport', 'Vnukovo_International_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Dubrovnik_Airport', 'Athens_International_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
0,0,"('Vnukovo_International_Airport', 'Hévíz-Balaton_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",graph-attribute-fact,,,
1,1,"('Vnukovo_International_Airport', 'Riga_International_Airport')",isConnectedTo,"[0.0,1.0]","[1.0,1.0]",connected_rule_1,"[('Riga_International_Airport', 'Amsterdam_Airport_Schiphol')]",['Amsterdam_Airport_Schiphol'],['Vnukovo_International_Airport']

Reading PyReasons Explainable Trace

When using the functions save_rule_trace and get_rule_trace, PyReason will output an explainable trace of the reasoning process.

In the trace, the columens represent the following:
  • time: the current timestep

  • Fixed-Point Operation:

  • Edge: The edge or node that has changed if applicable

  • Label: The predicate or head of the rule

  • Old Bound and New Bound: Bound before and after reasoning step

  • Occured Due to: what the the change in the step was due to, either fact or rule

  • Clause-x: What grounded the clause in the rule

Get Dictionary

The function interpretation.get_dict() can be called externally to retrieve a dictionary of the interpretation values. The dictionary is triply nested from time -> graph component -> predicate -> bound.

Basic Tutorial Example

To see interpretation.get_dict() in action we will look at the example usage in PyReasons Basic Tutorial.

Note

Find the full, explained tutorial here here

Call .get_dict() function on the interpretation, and print using pprint.

import pyreason as pr
from pprint import pprint

interpretation = pr.reason(timesteps=2)
interpretations_dict = interpretation.get_dict()
pprint(interpretations_dict)

Expected Output

Using .get_dict(), the expected output is:

{0: {'Cat': {},
    'Dog': {},
    'John': {},
    'Justin': {},
    'Mary': {'popular': (1.0, 1.0)},
    ('John', 'Dog'): {},
    ('John', 'Justin'): {},
    ('John', 'Mary'): {},
    ('Justin', 'Cat'): {},
    ('Justin', 'Dog'): {},
    ('Justin', 'Mary'): {},
    ('Mary', 'Cat'): {}},
1: {'Cat': {},
    'Dog': {},
    'John': {},
    'Justin': {'popular': (1.0, 1.0)},
    'Mary': {'popular': (1.0, 1.0)},
    ('John', 'Dog'): {},
    ('John', 'Justin'): {},
    ('John', 'Mary'): {},
    ('Justin', 'Cat'): {},
    ('Justin', 'Dog'): {},
    ('Justin', 'Mary'): {},
    ('Mary', 'Cat'): {}},
2: {'Cat': {},
    'Dog': {},
    'John': {'popular': (1.0, 1.0)},
    'Justin': {'popular': (1.0, 1.0)},
    'Mary': {'popular': (1.0, 1.0)},
    ('John', 'Dog'): {},
    ('John', 'Justin'): {},
    ('John', 'Mary'): {},
    ('Justin', 'Cat'): {},
    ('Justin', 'Dog'): {},
    ('Justin', 'Mary'): {},
    ('Mary', 'Cat'): {}}}

interpretation.get_dict() first goes through each time step, then the components of the graph, and finally the predicates and bounds.

Querying Individual Atoms

The interpretation.query() helper lets you inspect the final interpretation for a specific node or edge without iterating through the full trace. It evaluates a Query object against the stored interpretation and can either return a boolean answer or the numerical bounds that were inferred.

  1. Build a Query instance with the component (node or edge), predicate, and optional bound to check.

  2. Call interpretation.query(query) to get a boolean answer. The method returns True when the component exists, the predicate was produced during reasoning, and its final bound lies inside the query’s interval.

  3. Pass return_bool=False to retrieve the lower and upper bounds instead of a boolean. When the component or predicate is missing, the method returns (0.0, 1.0) by default.

import pyreason as pr

# After running reasoning
interpretation = pr.reason(timesteps=1)

# Construct a query for a node
node_query = pr.Query('popular(Mary) : [0.9, 1.0]')
is_popular = interpretation.query(node_query)  # returns True/False

# Retrieve the numeric bounds instead of a boolean
popularity_bounds = interpretation.query(node_query, return_bool=False)  # (lower, upper)
print(f"Mary's popularity bounds: {popularity_bounds}")

Queries can reference edges using the two-component syntax inside the query string. The interpretation returns the bound on that edge label (and optionally, the inferred value).

# Query an edge label
edge_query = pr.Query('isConnectedTo(A, B)')
if interpretation.query(edge_query):
    print("A and B remain connected in the final interpretation")
else:
    print("The connection between A and B was not maintained")

Use interpretation.query() when you need a quick, programmatic check of the reasoning result for a single atom, or when you want to control how the answer is surfaced in your application code.