Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/circumscription_ex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import pyreason as pr
import networkx as nx
from pprint import pprint

pr.reset()
pr.reset_rules()

g = nx.DiGraph()

g.add_nodes_from(['cb_1', 'cb_2', 'l1', 'l2'])
g.add_edge('cb_1', 'cb_2', stepFrom =1)
g.add_edge('cb_1', 'l1', hasLabel = 1)
g.add_edge('cb_2', 'l2', hasLabel = 1)
#g.add_edge('l1','l2', cond=1) # Adding this edge will

pr.settings.verbose = True
pr.settings.atom_trace = True
pr.settings.inconsistency_check = True

pr.load_graph(g)

pr.add_closed_world_predicate('hackerControl')
pr.add_fact(pr.Fact('stepFrom(cb_1, cb_2)', 'step_from_fact', 0, 1))
pr.add_fact(pr.Fact('hackerControl(cb_1)', 'hacker_control_initial_fact'))
pr.add_rule(pr.Rule('future(Y) <-1 stepFrom(X,Y), hackerControl(X)'))
pr.add_rule(pr.Rule('hackerControl(Y) <-1 hackerControl(X), hasLabel(X,L1), hasLabel(Y,L2), cond(L1, L2), stepFrom(X,Y)', 'hacker-control-rule'))
pr.add_rule(pr.Rule('hackerControl(Y) <-1 hackerControl(X), hasLabel(X,L1), hasLabel(Y,L2), cond_1(L1, L2), stepFrom(X,Y)', 'hacker-control-rule-1'))
pr.add_rule(pr.Rule('inconsistent(Y) <- future(Y), ~hackerControl(Y), ~hackerControl(X)', 'inconsistent_rule'))


interpretation = pr.reason(timesteps=2)
interp_dict = interpretation.get_dict()

pprint(interp_dict)

# Filter and sort nodes based on hackerControl
dataframes = pr.filter_and_sort_nodes(interpretation, ['hackerControl'])
for t, df in enumerate(dataframes):
print(f'TIMESTEP - {t}')
print(df)
print()

# Filter and sort edges based on inconsistent
edge_dataframes = pr.filter_and_sort_nodes(interpretation, ['inconsistent'])
for t, df in enumerate(edge_dataframes):
print(f'TIMESTEP - {t} (inconsistent nodes)')
print(df)
print()
21 changes: 20 additions & 1 deletion pyreason/pyreason.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ def fp_version(self, value: bool) -> None:
__ipl: Optional[numba.typed.List] = None
__specific_node_labels: Optional[numba.typed.List] = None
__specific_edge_labels: Optional[numba.typed.List] = None
__closed_world_predicates = set()

__non_fluent_graph_facts_node: Optional[numba.typed.List] = None
__non_fluent_graph_facts_edge: Optional[numba.typed.List] = None
Expand All @@ -486,12 +487,13 @@ def reset():
"""Resets certain variables to None to be able to do pr.reason() multiple times in a program
without memory blowing up
"""
global __node_facts, __edge_facts, __graph, __facts_name_set
global __node_facts, __edge_facts, __graph, __facts_name_set, __closed_world_predicates

# Facts
__node_facts = None
__edge_facts = None
__facts_name_set.clear()
__closed_world_predicates = set()
if __program is not None:
__program.reset_facts()

Expand Down Expand Up @@ -1085,6 +1087,17 @@ def _parse_and_validate_fact_params(idx, name_raw, start_time_raw, end_time_raw,
return name, start_time, end_time, static


def add_closed_world_predicate(predicate_name: str) -> None:
"""Register a predicate as closed_world (circumscription). For any node/edge where
a closed_world predicate has bounds [0,1] (unknown), it will be treated as [0,0] (false)
during rule satisfaction checks.

:param predicate_name: The name of the predicate to minimize
:return: None
"""
__closed_world_predicates.add(predicate_name)


def add_fact(pyreason_fact: Fact) -> None:
"""Add a PyReason fact to the program.

Expand Down Expand Up @@ -1510,6 +1523,12 @@ def _reason(timesteps, convergence_threshold, convergence_bound_threshold, queri
__program.specific_node_labels = __specific_node_labels
__program.specific_edge_labels = __specific_edge_labels

# Convert closed_world predicates to numba-compatible list of label types
closed_world_preds_numba = numba.typed.List.empty_list(label.label_type)
for pred_name in __closed_world_predicates:
closed_world_preds_numba.append(label.Label(pred_name))
__program.closed_world_predicates = closed_world_preds_numba

# Run Program and get final interpretation
interpretation = __program.reason(timesteps, convergence_threshold, convergence_bound_threshold, settings.verbose)

Expand Down
79 changes: 50 additions & 29 deletions pyreason/scripts/interpretation/interpretation.py

Large diffs are not rendered by default.

79 changes: 50 additions & 29 deletions pyreason/scripts/interpretation/interpretation_fp.py

Large diffs are not rendered by default.

79 changes: 50 additions & 29 deletions pyreason/scripts/interpretation/interpretation_parallel.py

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pyreason/scripts/program/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
class Program:
specific_node_labels = []
specific_edge_labels = []
closed_world_predicates = []

def __init__(self, graph, facts_node, facts_edge, rules, ipl, annotation_functions, head_functions, reverse_graph, atom_trace, save_graph_attributes_to_rule_trace, canonical, inconsistency_check, store_interpretation_changes, parallel_computing, update_mode, allow_ground_rules, fp_version):
self._graph = graph
Expand All @@ -30,8 +31,12 @@ def __init__(self, graph, facts_node, facts_edge, rules, ipl, annotation_functio
def reason(self, tmax, convergence_threshold, convergence_bound_threshold, verbose=True):
self._tmax = tmax
# Set up available labels
#TODO: Investigate issues w/ not adding specific edge and node labels to other interps
Interpretation.specific_node_labels = self.specific_node_labels
Interpretation.specific_edge_labels = self.specific_edge_labels
Interpretation.closed_world_predicates = self.closed_world_predicates
InterpretationFP.closed_world_predicates = self.closed_world_predicates
InterpretationParallel.closed_world_predicates = self.closed_world_predicates

# Instantiate correct interpretation class based on whether we parallelize the code or not. (We cannot parallelize with cache on)
if self._parallel_computing:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name='pyreason',
version='3.4.0',
version='3.5.0',
author='Dyuman Aditya',
author_email='dyuman.aditya@gmail.com',
description='An explainable inference software supporting annotated, real valued, graph based and temporal logic',
Expand Down
49 changes: 49 additions & 0 deletions tests/api_tests/test_pyreason_state_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from unittest.mock import patch, MagicMock
import pyreason as pr
import pyreason.pyreason as pr_mod


class TestResetFunction:
Expand Down Expand Up @@ -295,3 +296,51 @@ def test_full_cleanup_sequence(self):
assert pr.settings.verbose is True
assert pr.settings.memory_profile is False
assert pr.settings.output_file_name == "pyreason_output"


def _get_closed_world():
"""Access the internal closed_world predicates set."""
return pr_mod.__dict__['__closed_world_predicates']


class Testclosed_worldPredicateApi:
"""Test add_closed_world_predicate() and its interaction with reset functions."""

def setup_method(self):
pr.reset()
pr.reset_rules()

def teardown_method(self):
pr.reset()
pr.reset_rules()

def test_add_closed_world_predicate_registers(self):
pr.add_closed_world_predicate('hackerControl')
assert 'hackerControl' in _get_closed_world()

def test_add_multiple_closed_world_predicates(self):
pr.add_closed_world_predicate('pred_a')
pr.add_closed_world_predicate('pred_b')
pr.add_closed_world_predicate('pred_c')
assert {'pred_a', 'pred_b', 'pred_c'} == _get_closed_world()

def test_add_duplicate_is_idempotent(self):
pr.add_closed_world_predicate('pred')
pr.add_closed_world_predicate('pred')
assert _get_closed_world() == {'pred'}

def test_reset_clears_closed_world_predicates(self):
pr.add_closed_world_predicate('pred_a')
pr.add_closed_world_predicate('pred_b')
pr.reset()
assert len(_get_closed_world()) == 0

def test_reset_rules_does_not_clear_closed_world_predicates(self):
pr.add_closed_world_predicate('pred')
pr.reset_rules()
assert 'pred' in _get_closed_world()

def test_reset_settings_does_not_clear_closed_world_predicates(self):
pr.add_closed_world_predicate('pred')
pr.reset_settings()
assert 'pred' in _get_closed_world()
Loading
Loading