Skip to content
73 changes: 72 additions & 1 deletion tests/functional/test_advanced_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pyreason import Threshold
import torch
import torch.nn as nn
import networkx as nx
import numba
import numpy as np
import pytest
Expand Down Expand Up @@ -31,8 +32,10 @@ def probability_func(annotations, weights):
return union_prob, 1


def test_probability_func_consistency():
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_probability_func_consistency(mode):
"""Ensure annotation function behaves the same with and without JIT."""
setup_mode(mode)
annotations = numba.typed.List()
annotations.append(numba.typed.List([closed(0.01, 1.0)]))
annotations.append(numba.typed.List([closed(0.2, 1.0)]))
Expand Down Expand Up @@ -172,3 +175,71 @@ def test_classifier_integration(mode):
print("\nGenerated PyReason Facts:")
for fact in facts:
print(fact)


@pytest.mark.skipif(True, reason="Reason again functionality not implemented for FP version")
@pytest.mark.parametrize("mode", ["regular"])
def test_reason_again(mode):
"""Test reasoning continuation functionality."""
setup_mode(mode)

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/functional/friends_graph.graphml'

# Load all the files into pyreason
pr.load_graphml(graph_path)
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, 1))

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

# Now reason again
new_fact = pr.Fact('popular(Mary)', 'popular_fact2', 2, 4)
pr.add_fact(new_fact)
interpretation = pr.reason(timesteps=3, again=True, restart=False)

# 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()

assert len(dataframes[2]) == 1, 'At t=0 there should be one popular person'
assert len(dataframes[3]) == 2, 'At t=1 there should be two popular people'
assert len(dataframes[4]) == 3, 'At t=2 there should be three popular people'

# Mary should be popular in all three timesteps
assert 'Mary' in dataframes[2]['component'].values and dataframes[2].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=0 timesteps'
assert 'Mary' in dataframes[3]['component'].values and dataframes[3].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=1 timesteps'
assert 'Mary' in dataframes[4]['component'].values and dataframes[4].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=2 timesteps'

# Justin should be popular in timesteps 1, 2
assert 'Justin' in dataframes[3]['component'].values and dataframes[3].iloc[1].popular == [1, 1], 'Justin should have popular bounds [1,1] for t=1 timesteps'
assert 'Justin' in dataframes[4]['component'].values and dataframes[4].iloc[2].popular == [1, 1], 'Justin should have popular bounds [1,1] for t=2 timesteps'

# John should be popular in timestep 3
assert 'John' in dataframes[4]['component'].values and dataframes[4].iloc[1].popular == [1, 1], 'John should have popular bounds [1,1] for t=2 timesteps'


@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_reason_with_queries(mode):
"""Test reasoning with query-based rule filtering"""
setup_mode(mode)
# Set up test scenario
graph = nx.DiGraph()
graph.add_edges_from([("A", "B"), ("B", "C")])
pr.load_graph(graph)

pr.add_rule(pr.Rule('popular(x) <-1 friend(x, y)', 'rule1'))
pr.add_rule(pr.Rule('friend(x, y) <-1 knows(x, y)', 'rule2'))
pr.add_fact(pr.Fact('knows(A, B)', 'fact1'))

# Create query to filter rules
query = pr.Query('popular(A)')
pr.settings.verbose = False # Reduce output noise

interpretation = pr.reason(timesteps=1, queries=[query])
# Should complete and apply rule filtering logic
Empty file.
183 changes: 18 additions & 165 deletions tests/functional/test_anyBurl_infer_edges_rules.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,23 @@
import pyreason as pr
import pytest


@pytest.mark.slow
def test_anyBurl_rule_1():
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
def setup_mode(mode):
"""Configure PyReason settings for the specified mode."""
pr.reset()
pr.reset_rules()
pr.reset_settings()
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
pr.settings.inconsistency_check = False
pr.settings.static_graph_facts = False
pr.settings.output_to_file = False
pr.settings.store_interpretation_changes = True
pr.settings.save_graph_attributes_to_trace = True
# Load all the files into pyreason
pr.load_graphml(graph_path)
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)
# pr.save_rule_trace(interpretation)

# 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()
assert len(dataframes) == 2, 'Pyreason should run exactly 2 fixpoint operations'
assert len(dataframes[1]) == 1, 'At t=1 there should be only 1 new isConnectedTo atom'
assert ('Vnukovo_International_Airport', 'Riga_International_Airport') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Vnukovo_International_Airport, Riga_International_Airport) should have isConnectedTo bounds [1,1] for t=1 timesteps'

if mode == "fp":
pr.settings.fp_version = True
elif mode == "parallel":
pr.settings.parallel_computing = True

@pytest.mark.slow
def test_anyBurl_rule_2():
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_anyBurl_rule_1(mode):
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
setup_mode(mode)
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.atom_trace = True
Expand All @@ -53,119 +28,6 @@ def test_anyBurl_rule_2():
pr.settings.output_to_file = False
pr.settings.store_interpretation_changes = True
pr.settings.save_graph_attributes_to_trace = True
pr.settings.parallel_computing = False
# Load all the files into pyreason
pr.load_graphml(graph_path)

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

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

# 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()
assert len(dataframes) == 2, 'Pyreason should run exactly 2 fixpoint operations'
assert len(dataframes[1]) == 1, 'At t=1 there should be only 1 new isConnectedTo atom'
assert ('Riga_International_Airport', 'Vnukovo_International_Airport') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Riga_International_Airport, Vnukovo_International_Airport) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.slow
def test_anyBurl_rule_3():
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
pr.settings.inconsistency_check = False
pr.settings.static_graph_facts = False
pr.settings.output_to_file = False
pr.settings.store_interpretation_changes = True
pr.settings.save_graph_attributes_to_trace = True
pr.settings.parallel_computing = False
# Load all the files into pyreason
pr.load_graphml(graph_path)

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

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

# 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()
assert len(dataframes) == 2, 'Pyreason should run exactly 1 fixpoint operations'
assert len(dataframes[1]) == 1, 'At t=1 there should be only 1 new isConnectedTo atom'
assert ('Vnukovo_International_Airport', 'Yali') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Vnukovo_International_Airport, Yali) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.slow
def test_anyBurl_rule_4():
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
pr.settings.inconsistency_check = False
pr.settings.static_graph_facts = False
pr.settings.output_to_file = False
pr.settings.store_interpretation_changes = True
pr.settings.save_graph_attributes_to_trace = True
pr.settings.parallel_computing = False
# Load all the files into pyreason
pr.load_graphml(graph_path)

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

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

# 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()
assert len(dataframes) == 2, 'Pyreason should run exactly 1 fixpoint operations'
assert len(dataframes[1]) == 1, 'At t=1 there should be only 1 new isConnectedTo atom'
assert ('Yali', 'Vnukovo_International_Airport') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Yali, Vnukovo_International_Airport) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.fp
@pytest.mark.slow
def test_anyBurl_rule_1_fp():
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.fp_version = True # Use the FP version of the reasoner
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
pr.settings.inconsistency_check = False
pr.settings.static_graph_facts = False
pr.settings.output_to_file = False
pr.settings.store_interpretation_changes = True
pr.settings.save_graph_attributes_to_trace = True
# Load all the files into pyreason
pr.load_graphml(graph_path)
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))
Expand All @@ -185,16 +47,13 @@ def test_anyBurl_rule_1_fp():
assert ('Vnukovo_International_Airport', 'Riga_International_Airport') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Vnukovo_International_Airport, Riga_International_Airport) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.fp
@pytest.mark.slow
def test_anyBurl_rule_2_fp():
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_anyBurl_rule_2(mode):
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
setup_mode(mode)
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.fp_version = True # Use the FP version of the reasoner
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
Expand Down Expand Up @@ -224,16 +83,13 @@ def test_anyBurl_rule_2_fp():
assert ('Riga_International_Airport', 'Vnukovo_International_Airport') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Riga_International_Airport, Vnukovo_International_Airport) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.fp
@pytest.mark.slow
def test_anyBurl_rule_3_fp():
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_anyBurl_rule_3(mode):
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
setup_mode(mode)
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.fp_version = True # Use the FP version of the reasoner
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
Expand Down Expand Up @@ -263,16 +119,13 @@ def test_anyBurl_rule_3_fp():
assert ('Vnukovo_International_Airport', 'Yali') in dataframes[1]['component'].values.tolist() and dataframes[1]['isConnectedTo'].iloc[0] == [1, 1], '(Vnukovo_International_Airport, Yali) should have isConnectedTo bounds [1,1] for t=1 timesteps'


@pytest.mark.fp
@pytest.mark.slow
def test_anyBurl_rule_4_fp():
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
def test_anyBurl_rule_4(mode):
graph_path = './tests/functional/knowledge_graph_test_subset.graphml'
pr.reset()
pr.reset_rules()
pr.reset_settings()
setup_mode(mode)
# Modify pyreason settings to make verbose and to save the rule trace to a file
pr.settings.verbose = True
pr.settings.fp_version = True # Use the FP version of the reasoner
pr.settings.atom_trace = True
pr.settings.memory_profile = False
pr.settings.canonical = True
Expand Down
51 changes: 1 addition & 50 deletions tests/functional/test_basic_reasoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,55 +59,6 @@ def test_hello_world(mode):
# John should be popular in timestep 3
assert 'John' in dataframes[2]['component'].values and dataframes[2].iloc[1].popular == [1, 1], 'John should have popular bounds [1,1] for t=2 timesteps'


@pytest.mark.slow
def test_hello_world_consistency():
"""Test consistency between JIT and pure Python implementations."""
# Reset PyReason
pr.reset()
pr.reset_rules()
pr.reset_settings()

# Modify the paths based on where you've stored the files we made above
graph_path = './tests/functional/friends_graph.graphml'

# Modify pyreason settings to make verbose
pr.settings.verbose = True # Print info to screen
pr.settings.atom_trace = True # Print atom trace
pr.settings.test_inconsistency = True

# Load all the files into pyreason
pr.load_graphml(graph_path)
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
interpretation = pr.reason(timesteps=2)

# 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()

assert len(dataframes[0]) == 1, 'At t=0 there should be one popular person'
assert len(dataframes[1]) == 2, 'At t=1 there should be two popular people'
assert len(dataframes[2]) == 3, 'At t=2 there should be three popular people'

# Mary should be popular in all three timesteps
assert 'Mary' in dataframes[0]['component'].values and dataframes[0].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=0 timesteps'
assert 'Mary' in dataframes[1]['component'].values and dataframes[1].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=1 timesteps'
assert 'Mary' in dataframes[2]['component'].values and dataframes[2].iloc[0].popular == [1, 1], 'Mary should have popular bounds [1,1] for t=2 timesteps'

# Justin should be popular in timesteps 1, 2
assert 'Justin' in dataframes[1]['component'].values and dataframes[1].iloc[1].popular == [1, 1], 'Justin should have popular bounds [1,1] for t=1 timesteps'
assert 'Justin' in dataframes[2]['component'].values and dataframes[2].iloc[2].popular == [1, 1], 'Justin should have popular bounds [1,1] for t=2 timesteps'

# John should be popular in timestep 3
assert 'John' in dataframes[2]['component'].values and dataframes[2].iloc[1].popular == [1, 1], 'John should have popular bounds [1,1] for t=2 timesteps'


@pytest.mark.parametrize("mode", ["regular", "fp"])
def test_reorder_clauses(mode):
"""Test clause reordering functionality."""
Expand Down Expand Up @@ -161,4 +112,4 @@ def test_reorder_clauses(mode):
assert first_justin_rule['Clause-1'][0] == ('Justin', 'Mary')
else:
# Regular version: The second row, clause 1 should be the edge grounding ('Justin', 'Mary')
assert rule_trace_node.iloc[2]['Clause-1'][0] == ('Justin', 'Mary')
assert rule_trace_node.iloc[2]['Clause-1'][0] == ('Justin', 'Mary')
Loading