Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
611d3e0
Update website link in README.md
pshak02 Jan 15, 2026
972b263
Add fact parser validation, tests, and bulk fact loading with tests
ColtonPayne Jan 21, 2026
e498f4a
Don't hardcode default values
ColtonPayne Jan 21, 2026
0e2e395
Prevent predicates from starting with a digit
ColtonPayne Jan 21, 2026
cf592e2
Fix typo in MAKEFILE
ColtonPayne Jan 21, 2026
0908471
Improve CSV loader tests
ColtonPayne Jan 21, 2026
5a78cea
Add fact string formatting rules in docstring
ColtonPayne Jan 21, 2026
83a2452
Remove extranious f string for linter
ColtonPayne Jan 21, 2026
ee0ea04
Fix api test file loading
ColtonPayne Jan 21, 2026
f1395dc
Add test for example with no header
ColtonPayne Jan 21, 2026
0e3db89
Make invalid csv file loads raise exceptions by default
ColtonPayne Jan 25, 2026
a402321
Upd tests
ColtonPayne Jan 25, 2026
882f71d
Fix BUG-138: Correct threshold checking by filtering qualified ground…
ColtonPayne Jan 26, 2026
2e8ecbf
Fix test compatibility with BUG-138 fix
ColtonPayne Jan 26, 2026
82b0da7
Sync interpretation parallel
ColtonPayne Jan 26, 2026
392dbe5
Cleanup prints and update pre-commit hook
ColtonPayne Jan 26, 2026
5420457
Update sync interpretation
ColtonPayne Jan 26, 2026
4c1c44a
Add tests for edge groundings
ColtonPayne Jan 26, 2026
8cec9a0
Cleanup prints in tests
ColtonPayne Jan 26, 2026
5aa31f0
one more sync test
ColtonPayne Jan 26, 2026
240260e
Add cache status back
ColtonPayne Jan 26, 2026
1e61729
Sync interp parrallel
ColtonPayne Jan 28, 2026
0ca4abd
Move to test_reason_update
ColtonPayne Jan 28, 2026
b4abc69
Delete tests/unit/disable_jit/interpretations/test_bug_127_delta_boun…
ColtonPayne Jan 28, 2026
3890a78
Sync interp parrallel
ColtonPayne Jan 28, 2026
c8d69ee
Merge branch 'fix-bug-125-127' of github.com:lab-v2/pyreason into fix…
ColtonPayne Jan 28, 2026
7881db3
Update contact information in README.md
kmukherji Jan 28, 2026
bf944d9
Don't change the cache status
ColtonPayne Jan 30, 2026
c3266ae
Cleanup prints in tests
ColtonPayne Jan 30, 2026
fa3eccb
Merge pull request #101 from lab-v2/fix-bug-138
ColtonPayne Jan 30, 2026
d1cb309
Add support for negated interval and negated explicit true/false
ColtonPayne Jan 30, 2026
25236cd
Merge pull request #102 from lab-v2/fix-bug-125-127
ColtonPayne Jan 30, 2026
b903281
Load facts from json instead of csv
ColtonPayne Jan 30, 2026
94e2d74
Initial commit
ColtonPayne Jan 30, 2026
cad2d95
Update file loading tests
ColtonPayne Jan 30, 2026
3eecf32
Revert
ColtonPayne Jan 30, 2026
99e7126
test updates
ColtonPayne Jan 30, 2026
05b3748
Final cleanup
ColtonPayne Jan 31, 2026
1401068
Sync file loading tests
ColtonPayne Jan 31, 2026
960c573
Cache status
ColtonPayne Jan 31, 2026
0f4d8cf
Test updates
ColtonPayne Jan 31, 2026
60616cd
Match tests with updates
ColtonPayne Jan 31, 2026
0c79988
Add back csv file loading and add duplicate name checks
ColtonPayne Feb 3, 2026
b1ea06c
Merge branch 'main' into input-validation
ColtonPayne Feb 3, 2026
01a20a4
CSV Formatting
ColtonPayne Feb 4, 2026
42d54e2
Add back load rules from file
ColtonPayne Feb 4, 2026
f4fbcb0
Merge branch 'input-validation' of github.com:lab-v2/pyreason into in…
ColtonPayne Feb 4, 2026
3ba07ec
Requrie exact header match for csv headers
ColtonPayne Feb 4, 2026
38ddb3e
Rem fact file loading tests from wrong PR
ColtonPayne Feb 4, 2026
c65c6bf
Enable bounds with true/false string literals and other test updates
ColtonPayne Feb 4, 2026
17db396
Status yaml
ColtonPayne Feb 4, 2026
c846b1a
Del test file
ColtonPayne Feb 4, 2026
cbe0195
Define all floats in interval methods to be float64 to match constructor
ColtonPayne Feb 4, 2026
1113443
Fix float_to_str for negative values and missing zero-padding
ColtonPayne Feb 4, 2026
8485b66
Add bulk rule loading from CSV and JSON files
ColtonPayne Feb 6, 2026
805208b
Have fact loader use bool helper function
ColtonPayne Feb 6, 2026
6359b42
Fact loader must support thresholds as a list or a dict
ColtonPayne Feb 7, 2026
21e5e6e
Merge pull request #119 from lab-v2/fix-bug-102-103
ColtonPayne Feb 10, 2026
a3d86e5
Merge branch 'main' into fix-bug-001-007
ColtonPayne Feb 10, 2026
930c32b
Update examples and remove numeric string fact loading
ColtonPayne Feb 10, 2026
4928d98
Merge branch 'main' into input-validation
ColtonPayne Feb 10, 2026
377940c
Merge pull request #118 from lab-v2/fix-bug-001-007
ColtonPayne Feb 10, 2026
b3cf9b0
Update error messages and support empty body in rule
ColtonPayne Feb 10, 2026
4c0511d
Merge branch 'main' into add-rule-parser-validation
ColtonPayne Feb 10, 2026
e14eb9f
Add back static string bulk csv
ColtonPayne Feb 10, 2026
8fa0ef7
Merge branch 'input-validation' of github.com:lab-v2/pyreason into in…
ColtonPayne Feb 10, 2026
7c489c7
Merge pull request #103 from lab-v2/add-rule-parser-validation
kmukherji Feb 10, 2026
27add9f
Merge branch 'main' into input-validation
ColtonPayne Feb 10, 2026
b975b08
Set default end_time to start_time
ColtonPayne Feb 10, 2026
a6fb069
Merge branch 'input-validation' of github.com:lab-v2/pyreason into in…
ColtonPayne Feb 10, 2026
7594583
Merge pull request #100 from lab-v2/input-validation
ColtonPayne Feb 10, 2026
f194205
Merge branch 'main' into add-bulk-rule-loading
ColtonPayne Feb 10, 2026
ac3bd10
Update function name from variable to component
ColtonPayne Feb 11, 2026
a986ba6
Merge pull request #120 from lab-v2/add-bulk-rule-loading
ColtonPayne Feb 13, 2026
0c86c4a
Bump version to 3.3.0 for PyPI release
ColtonPayne Feb 13, 2026
1c796e4
Merge pull request #128 from lab-v2/bump-version-3.3.0
ColtonPayne Feb 13, 2026
7cce362
Detailed atom trace
ColtonPayne Feb 21, 2026
901aad9
Fix functional test
ColtonPayne Feb 21, 2026
98263f2
Revert "Fix functional test"
ColtonPayne Feb 22, 2026
2e607fc
fix too few values error
ColtonPayne Mar 8, 2026
5c5eb78
Count graph attributes
ColtonPayne Mar 10, 2026
d9aadfb
Fix typo
ColtonPayne Mar 10, 2026
f8b92c2
Test partial edge grounding
ColtonPayne Mar 11, 2026
77510f1
Test partial edge grounding
ColtonPayne Mar 11, 2026
00de233
Add functional tests for partial edge grounding
ColtonPayne Mar 16, 2026
08e8d02
Revert "Test partial edge grounding"
ColtonPayne Mar 16, 2026
c47f384
Cache Status
ColtonPayne Mar 18, 2026
f415eca
Update traces to be conistent with previous examples
ColtonPayne Mar 18, 2026
117714f
Merge pull request #135 from lab-v2/test-partial-edge-grounding
ColtonPayne Mar 18, 2026
0d7a165
Merge branch 'main' into detailed-atom-trace-2
ColtonPayne Mar 18, 2026
c3bcb3b
Merge pull request #134 from lab-v2/detailed-atom-trace-2
ColtonPayne Mar 18, 2026
a2367f7
Bump version
ColtonPayne Mar 18, 2026
477ec63
Merge pull request #136 from lab-v2/bump-release-3.3.4
ColtonPayne Mar 18, 2026
af0ad85
Add debug prints
ColtonPayne Apr 4, 2026
a8741cd
Initial circumscription
ColtonPayne Apr 5, 2026
8cb7baa
Add check for empty dict
ColtonPayne Apr 5, 2026
214fec6
Rem debug
ColtonPayne Apr 5, 2026
160a729
Sync parallel
ColtonPayne Apr 5, 2026
191ecd0
Test updates
ColtonPayne Apr 6, 2026
4b4dc3d
Fix functional tests
ColtonPayne Apr 6, 2026
d240a22
Rem debug test
ColtonPayne Apr 6, 2026
4285ac6
Add missing dict test
ColtonPayne Apr 6, 2026
31743ab
Replace minimized with closed_world
ColtonPayne Apr 6, 2026
61b8077
Rename tests
ColtonPayne Apr 6, 2026
ecec9aa
Bump PyReason Version
ColtonPayne Apr 6, 2026
f76ab30
Update minimized predicate example
ColtonPayne Apr 6, 2026
cecfd97
Merge pull request #137 from lab-v2/add-circumscription-2
ColtonPayne Apr 6, 2026
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
27 changes: 11 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,56 +1,51 @@
exclude: 'pyreason/\.cache_status\.yaml'
exclude: 'pyreason/\.cache_status\.yaml|pyreason/scripts/interpretation/interpretation_parallel\.py'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: end-of-file-fixer
name: Fix end of files
exclude: 'pyreason/scripts/interpretation/interpretation_parallel\.py'
stages: [pre-commit]

- repo: local
hooks:
# --- COMMIT STAGE: Fast unit tests only ---
- id: ruff-check
name: Run ruff linter
entry: .venv/bin/python -m ruff check pyreason/scripts
entry: ruff check pyreason/scripts
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]

- id: sync-interpretation-parallel
name: Sync interpretation_parallel.py from interpretation.py
entry: .venv/bin/python sync_interpretation_parallel.py
language: system
pass_filenames: false
files: 'pyreason/scripts/interpretation/interpretation\.py'
stages: [pre-commit]


- id: pytest-unit-no-jit
name: Run JIT-disabled unit tests
entry: .venv/bin/python -m pytest tests/unit/disable_jit -m "not slow" --tb=short -q
entry: pytest tests/unit/disable_jit --tb=short -q
language: system
pass_filenames: false
stages: [pre-commit]
require_serial: true

- id: pytest-unit-jit
name: Run JIT-enabled unit tests
entry: .venv/bin/python -m pytest tests/unit/dont_disable_jit -m "not slow" --tb=short -q
entry: pytest tests/unit/dont_disable_jit --tb=short -q
language: system
pass_filenames: false
stages: [pre-commit]
require_serial: true

# --- PUSH STAGE: Complete test suite ---
- id: pytest-unit-api
name: Run pyreason api tests
entry: .venv/bin/python -m pytest tests/api_tests --tb=short -q
entry: pytest tests/api_tests --tb=short -q
language: system
pass_filenames: false
stages: [pre-push]

- id: pytest-functional-complete
name: Run functional test suite
entry: .venv/bin/python -m pytest tests/functional/ --tb=short -q
entry: pytest tests/functional/ --tb=short -q
language: system
pass_filenames: false
stages: [pre-push]
stages: [pre-push]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ test-api: ## Run only API tests (tests/api_tests)

test-jit: ## Run only JIT-disabled tests (tests/unit/disable_jit)
@echo "$(BOLD)$(BLUE)Running JIT-disabled tests...$(RESET)"
$(RUN_TESTS) --suite don_disable_jit
$(RUN_TESTS) --suite dont_disable_jit

test-no-jit: ## Run only JIT-enabled tests (tests/unit/dont_disable_jit)
@echo "$(BOLD)$(BLUE)Running JIT-enabled tests...$(RESET)"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ An explainable inference software supporting annotated, real valued, graph based

[📽️ Video](https://www.youtube.com/watch?v=E1PSl3KQCmo)

[🌐 Website](https://pyreason-staging.sites.syr.edu)
[🌐 Website](https://pyreason.syracuse.edu/)

[🏋️‍♂️ PyReason Gym](https://github.com/lab-v2/pyreason-gym)

Expand Down Expand Up @@ -64,7 +64,7 @@ Trademark Permission PyReason™ and PyReason Design Logo <img src="https://raw.


## 6. Contact
Dyuman Aditya - daditya@syr.edu
Colton Payne - crpayne@syr.edu

Kaustuv Mukherji - kmukherj@syr.edu

Expand Down
57 changes: 57 additions & 0 deletions examples/closed_world_pred_ex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 satisfy hackerControl(cb)

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

pr.load_graph(g)

# hackerControl is a closed-world pred, meaning that it will be grounded as [0,0] if its bounds are [0,1] (or if it is not in the interpretation dict)
pr.add_closed_world_predicate('hackerControl')

# Initial fact instantiation
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', 0, 0))

# Future(Y) will fire for cb_2
pr.add_rule(pr.Rule('future(Y) <-1 stepFrom(X,Y), hackerControl(X)'))

#This rule will not fire for cb_2, as cond(cb_1, cb_2) is not grounded
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'))

# At timestep 1, hackerControl(cb_1) and hackerControl(cb_2) have no associated bounds, so they are treated as [0,1].
# Because hackerControl is minimized, its bounds are gounded as [0,0]. Future(cb_2) has bounds [1,1], so inconsistent(cb_2) fires.
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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Time,Fixed-Point-Operation,Edge,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1,Clause-2,Clause-3
0,0,"('Alice', 'Bob')",close_contact,"[0.0,1.0]","[0.8,1.0]",alice_bob_close_fact,True,Fact,,,,
0,0,"('Alice', 'Bob')",no_contact,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: close_contact,True,IPL,,,,
0,0,"('Bob', 'Carol')",trust,"[0.0,1.0]","[0.9,1.0]",bob_carol_trust_high,True,Fact,,,,
0,1,"('Bob', 'Carol')",trust,"[0.9,1.0]","[0.0,1.0]",distrust_rule,False,Rule,"Inconsistency occurred. Conflicting bounds for trust(Bob,Carol). Update from [0.900, 1.000] to [0.000, 0.200] is not allowed. Setting bounds to [0,1] and static=True for this timestep.",['Bob'],"[('Bob', 'Carol')]",
0,1,"('Bob', 'Dave')",trust,"[0.0,1.0]","[0.0,0.2]",distrust_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]",
0,1,"('Bob', 'Carol')",risk,"[0.0,1.0]","[0.6,0.8]",risk_rule,True,Rule,,['Bob'],"[('Bob', 'Carol')]",
0,1,"('Bob', 'Dave')",risk,"[0.0,1.0]","[0.6,0.8]",risk_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]",
0,2,"('Bob', 'Dave')",no_contact,"[0.0,1.0]","[0.8,1.0]",quarantine_rule,True,Rule,,['Bob'],['Dave'],"[('Bob', 'Dave')]"
0,2,"('Bob', 'Dave')",close_contact,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: no_contact,True,IPL,,,,
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Time,Fixed-Point-Operation,Node,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1,Clause-2
0,0,Alice,sick,"[0.0,1.0]","[0.8,1.0]",alice_sick_fact,True,Fact,,,
0,0,Alice,healthy,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: sick,True,IPL,,,
0,0,Alice,healthy,"[0.0,0.19999999999999996]","[0.0,1.0]",alice_healthy_fact,False,Fact,"Inconsistency occurred. Grounding healthy(Alice) conflicts with grounding sick(Alice). Setting bounds to [0,1] and static=True for this timestep.",,
0,0,Alice,sick,"[0.8,1.0]","[0.0,1.0]",alice_healthy_fact,False,IPL,"Inconsistency occurred. Grounding healthy(Alice) conflicts with grounding sick(Alice). Setting bounds to [0,1] and static=True for this timestep.",,
0,0,Bob,sick,"[0.0,1.0]","[0.6,0.8]",bob_sick_fact,True,Fact,,,
0,0,Bob,healthy,"[0.0,1.0]","[0.19999999999999996,0.4]",IPL: sick,True,IPL,,,
0,0,Carol,healthy,"[0.0,1.0]","[0.9,1.0]",carol_healthy_fact,True,Fact,,,
0,0,Carol,sick,"[0.0,1.0]","[0.0,0.09999999999999998]",IPL: healthy,True,IPL,,,
0,0,Bob,tired,"[0.0,1.0]","[0.8,1.0]",bob_tired_fact_1,True,Fact,,,
0,0,Bob,tired,"[0.8,1.0]","[0.0,1.0]",bob_tired_fact_2,False,Fact,"Inconsistency occurred. Conflicting bounds for tired(Bob). Update from [0.800, 1.000] to [0.000, 0.100] is not allowed. Setting bounds to [0,1] and static=True for this timestep.",,
0,1,Carol,sick,"[0.0,0.09999999999999998]","[0.0,1.0]",spread_rule,False,Rule,"Inconsistency occurred. Grounding sick(Carol) conflicts with grounding healthy(Carol). Setting bounds to [0,1] and static=True for this timestep.",['Bob'],"[('Bob', 'Carol')]"
0,1,Carol,healthy,"[0.9,1.0]","[0.0,1.0]",spread_rule,False,IPL,"Inconsistency occurred. Grounding sick(Carol) conflicts with grounding healthy(Carol). Setting bounds to [0,1] and static=True for this timestep.",,
0,1,Dave,sick,"[0.0,1.0]","[0.5,0.7]",spread_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]"
0,1,Dave,healthy,"[0.0,1.0]","[0.30000000000000004,0.5]",IPL: sick,True,IPL,,,
140 changes: 140 additions & 0 deletions examples/inconsistency_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""
Example: Atom Trace with Inconsistencies
=========================================
This example demonstrates how PyReason detects and resolves inconsistencies
using the Inconsistent Predicate List (IPL) and how atom_trace provides
full explainability of what happened.

Scenario:
- We have a small health network: Alice, Bob, and Carol.
- "sick" and "healthy" are declared as inconsistent node predicates.
- "close_contact" and "no_contact" are declared as inconsistent edge predicates.
- Node inconsistencies:
1. sick(Alice) vs healthy(Alice) -- IPL-based conflict
2. tired(Bob) with non-overlapping bounds -- same-predicate conflict
- Edge inconsistencies (triggered by rules firing):
3. quarantine_rule infers no_contact(Alice,Bob) which conflicts with
the close_contact(Alice,Bob) fact via the IPL
4. distrust_rule infers trust(Bob,Carol):[0.0,0.2] which conflicts with
the trust(Bob,Carol):[0.9,1.0] fact -- same-predicate non-overlapping bounds
- All inconsistencies are resolved to [0, 1] (complete uncertainty).
- A rule propagates sickness through edges, showing normal (non-conflicting)
reasoning alongside the inconsistency resolution.
"""

import pyreason as pr
import networkx as nx

# Reset PyReason to a clean state
pr.reset()
pr.reset_rules()

# ================================ CREATE GRAPH ================================
g = nx.DiGraph()

# People in our health network
g.add_nodes_from(['Alice', 'Bob', 'Carol', 'Dave'])

# Contact edges (who has been in contact with whom)
g.add_edge('Alice', 'Bob', contact=1)
g.add_edge('Bob', 'Carol', contact=1)
g.add_edge('Bob', 'Dave', contact=1)

# ================================ CONFIGURE ===================================
pr.settings.verbose = True
pr.settings.atom_trace = True # Enable atom trace for full explainability
pr.settings.inconsistency_check = True # Enable inconsistency detection (default)

# ================================ LOAD GRAPH ==================================
pr.load_graph(g)

# Declare sick and healthy as inconsistent predicates
# When one is set, PyReason automatically gives the other the negated bound
pr.add_inconsistent_predicate('sick', 'healthy')

# Declare close_contact and no_contact as inconsistent edge predicates
pr.add_inconsistent_predicate('close_contact', 'no_contact')

# ================================ ADD RULES ===================================
# If someone is sick and in contact with another person, that person may get sick
pr.add_rule(pr.Rule('sick(y):[0.5,0.7] <- sick(x):[0.5,1.0], contact(x,y)', 'spread_rule'))

# Rule that infers no_contact on an edge when both people are sick -- will conflict
# with the close_contact fact on (Alice, Bob) via the IPL
pr.add_rule(pr.Rule('no_contact(x,y):[0.8,1.0] <- sick(x):[0.5,1.0], sick(y):[0.5,1.0], contact(x,y)', 'quarantine_rule'))

# Rule that infers low trust on an edge when someone is sick -- will conflict
# with the high trust fact on (Bob, Carol) via same-predicate conflicting bounds
pr.add_rule(pr.Rule('trust(x,y):[0.0,0.2] <- sick(x):[0.5,1.0], contact(x,y)', 'distrust_rule'))

# Rule that infers risk on an edge (no conflict -- clean edge rule for comparison)
pr.add_rule(pr.Rule('risk(x,y):[0.6,0.8] <- sick(x):[0.5,1.0], contact(x,y)', 'risk_rule'))

# ================================ ADD FACTS ===================================
# Fact 1: Alice is sick with high confidence
pr.add_fact(pr.Fact('sick(Alice):[0.8,1.0]', 'alice_sick_fact', 0, 0))

# Fact 2: Alice is also healthy with high confidence -- this CONTRADICTS Fact 1
# Since sick and healthy are in the IPL, this creates an inconsistency
pr.add_fact(pr.Fact('healthy(Alice):[0.9,1.0]', 'alice_healthy_fact', 0, 0))

# Fact 3: Bob is sick (no contradiction here, normal reasoning)
pr.add_fact(pr.Fact('sick(Bob):[0.6,0.8]', 'bob_sick_fact', 0, 0))
# Fact 3.5 : Carol is Healthy (will trigger contradiction later)
pr.add_fact(pr.Fact('healthy(Carol):[0.9,1.0]', 'carol_healthy_fact', 0, 0))

# Fact 4 & 5: Bob is tired with two conflicting, non-overlapping bounds
# Since "tired" is NOT in the IPL, this triggers a same-predicate inconsistency
pr.add_fact(pr.Fact('tired(Bob):[0.8,1.0]', 'bob_tired_fact_1', 0, 0))
pr.add_fact(pr.Fact('tired(Bob):[0.0,0.1]', 'bob_tired_fact_2', 0, 0))

# Dave has no conflicting predicates -- spread_rule will cleanly set sick(Dave)
# This provides a normal node rule trace entry for comparison

# ---- Edge inconsistencies (set up initial state for rule-triggered conflicts) ----
# Fact 6: Set close_contact on (Alice, Bob) -- quarantine_rule will later infer
# no_contact on the same edge, triggering an IPL-based edge inconsistency
pr.add_fact(pr.Fact('close_contact(Alice,Bob):[0.8,1.0]', 'alice_bob_close_fact', 0, 0))

# Fact 7: Set high trust on (Bob, Carol) -- distrust_rule will later infer
# trust:[0.0,0.2] on the same edge, triggering a same-predicate edge inconsistency
pr.add_fact(pr.Fact('trust(Bob,Carol):[0.9,1.0]', 'bob_carol_trust_high', 0, 0))

# ================================ REASON ======================================
print('=' * 60)
print('Running PyReason with inconsistency detection...')
print('=' * 60)
interpretation = pr.reason(timesteps=2)

# ================================ VIEW RESULTS ================================
print('\n' + '=' * 60)
print('Node Interpretation Changes (sick)')
print('=' * 60)
dataframes = pr.filter_and_sort_nodes(interpretation, ['sick'])
for t, df in enumerate(dataframes):
print(f'\nTIMESTEP {t}:')
print(df)

print('\n' + '=' * 60)
print('Node Interpretation Changes (healthy)')
print('=' * 60)
dataframes = pr.filter_and_sort_nodes(interpretation, ['healthy'])
for t, df in enumerate(dataframes):
print(f'\nTIMESTEP {t}:')
print(df)

# ================================ VIEW TRACE ==================================
print('\n' + '=' * 60)
print('Rule Trace (shows inconsistency resolution details)')
print('=' * 60)
node_trace, edge_trace = pr.get_rule_trace(interpretation)
print('\nNode trace:')
print(node_trace.to_string())

if not edge_trace.empty:
print('\nEdge trace:')
print(edge_trace.to_string())

# Save the rule trace to a file for further inspection
pr.save_rule_trace(interpretation)
print('\nRule trace saved to current directory.')
10 changes: 6 additions & 4 deletions pyreason/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
print('PyReason initialized!')
print()

# Update cache status
cache_status['initialized'] = True
with open(cache_status_path, 'w') as file:
yaml.dump(cache_status, file)
# Update cache status (skip under test runners to keep repo file clean)
import sys
if 'pytest' not in sys.modules and 'unittest' not in sys.modules:
cache_status['initialized'] = True
with open(cache_status_path, 'w') as file:
yaml.dump(cache_status, file)
Loading
Loading