Skip to content

Commit c3bcb3b

Browse files
authored
Merge pull request #134 from lab-v2/detailed-atom-trace-2
Detailed atom trace 2
2 parents 117714f + 0d7a165 commit c3bcb3b

20 files changed

Lines changed: 715 additions & 164 deletions

examples/csv outputs/advanced_rule_trace_edges_20241119-012153.csv renamed to examples/csv_outputs/advanced_rule_trace_edges_20241119-012153.csv

File renamed without changes.

examples/csv outputs/advanced_rule_trace_nodes_20241119-012153.csv renamed to examples/csv_outputs/advanced_rule_trace_nodes_20241119-012153.csv

File renamed without changes.

examples/csv outputs/basic_rule_trace_nodes_20241119-012005.csv renamed to examples/csv_outputs/basic_rule_trace_nodes_20241119-012005.csv

File renamed without changes.

examples/csv outputs/basic_rule_trace_nodes_20241125-114246.csv renamed to examples/csv_outputs/basic_rule_trace_nodes_20241125-114246.csv

File renamed without changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Time,Fixed-Point-Operation,Edge,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1,Clause-2,Clause-3
2+
0,0,"('Alice', 'Bob')",close_contact,"[0.0,1.0]","[0.8,1.0]",alice_bob_close_fact,True,Fact,,,,
3+
0,0,"('Alice', 'Bob')",no_contact,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: close_contact,True,IPL,,,,
4+
0,0,"('Bob', 'Carol')",trust,"[0.0,1.0]","[0.9,1.0]",bob_carol_trust_high,True,Fact,,,,
5+
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')]",
6+
0,1,"('Bob', 'Dave')",trust,"[0.0,1.0]","[0.0,0.2]",distrust_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]",
7+
0,1,"('Bob', 'Carol')",risk,"[0.0,1.0]","[0.6,0.8]",risk_rule,True,Rule,,['Bob'],"[('Bob', 'Carol')]",
8+
0,1,"('Bob', 'Dave')",risk,"[0.0,1.0]","[0.6,0.8]",risk_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]",
9+
0,2,"('Bob', 'Dave')",no_contact,"[0.0,1.0]","[0.8,1.0]",quarantine_rule,True,Rule,,['Bob'],['Dave'],"[('Bob', 'Dave')]"
10+
0,2,"('Bob', 'Dave')",close_contact,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: no_contact,True,IPL,,,,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Time,Fixed-Point-Operation,Node,Label,Old Bound,New Bound,Occurred Due To,Consistent,Triggered By,Inconsistency Message,Clause-1,Clause-2
2+
0,0,Alice,sick,"[0.0,1.0]","[0.8,1.0]",alice_sick_fact,True,Fact,,,
3+
0,0,Alice,healthy,"[0.0,1.0]","[0.0,0.19999999999999996]",IPL: sick,True,IPL,,,
4+
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.",,
5+
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.",,
6+
0,0,Bob,sick,"[0.0,1.0]","[0.6,0.8]",bob_sick_fact,True,Fact,,,
7+
0,0,Bob,healthy,"[0.0,1.0]","[0.19999999999999996,0.4]",IPL: sick,True,IPL,,,
8+
0,0,Carol,healthy,"[0.0,1.0]","[0.9,1.0]",carol_healthy_fact,True,Fact,,,
9+
0,0,Carol,sick,"[0.0,1.0]","[0.0,0.09999999999999998]",IPL: healthy,True,IPL,,,
10+
0,0,Bob,tired,"[0.0,1.0]","[0.8,1.0]",bob_tired_fact_1,True,Fact,,,
11+
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.",,
12+
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')]"
13+
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.",,
14+
0,1,Dave,sick,"[0.0,1.0]","[0.5,0.7]",spread_rule,True,Rule,,['Bob'],"[('Bob', 'Dave')]"
15+
0,1,Dave,healthy,"[0.0,1.0]","[0.30000000000000004,0.5]",IPL: sick,True,IPL,,,

examples/csv outputs/infer_edges_rule_trace_edges_20241119-140955.csv renamed to examples/csv_outputs/infer_edges_rule_trace_edges_20241119-140955.csv

File renamed without changes.

examples/csv outputs/infer_edges_rule_trace_nodes_20241119-140955.csv renamed to examples/csv_outputs/infer_edges_rule_trace_nodes_20241119-140955.csv

File renamed without changes.

examples/inconsistency_example.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
Example: Atom Trace with Inconsistencies
3+
=========================================
4+
This example demonstrates how PyReason detects and resolves inconsistencies
5+
using the Inconsistent Predicate List (IPL) and how atom_trace provides
6+
full explainability of what happened.
7+
8+
Scenario:
9+
- We have a small health network: Alice, Bob, and Carol.
10+
- "sick" and "healthy" are declared as inconsistent node predicates.
11+
- "close_contact" and "no_contact" are declared as inconsistent edge predicates.
12+
- Node inconsistencies:
13+
1. sick(Alice) vs healthy(Alice) -- IPL-based conflict
14+
2. tired(Bob) with non-overlapping bounds -- same-predicate conflict
15+
- Edge inconsistencies (triggered by rules firing):
16+
3. quarantine_rule infers no_contact(Alice,Bob) which conflicts with
17+
the close_contact(Alice,Bob) fact via the IPL
18+
4. distrust_rule infers trust(Bob,Carol):[0.0,0.2] which conflicts with
19+
the trust(Bob,Carol):[0.9,1.0] fact -- same-predicate non-overlapping bounds
20+
- All inconsistencies are resolved to [0, 1] (complete uncertainty).
21+
- A rule propagates sickness through edges, showing normal (non-conflicting)
22+
reasoning alongside the inconsistency resolution.
23+
"""
24+
25+
import pyreason as pr
26+
import networkx as nx
27+
28+
# Reset PyReason to a clean state
29+
pr.reset()
30+
pr.reset_rules()
31+
32+
# ================================ CREATE GRAPH ================================
33+
g = nx.DiGraph()
34+
35+
# People in our health network
36+
g.add_nodes_from(['Alice', 'Bob', 'Carol', 'Dave'])
37+
38+
# Contact edges (who has been in contact with whom)
39+
g.add_edge('Alice', 'Bob', contact=1)
40+
g.add_edge('Bob', 'Carol', contact=1)
41+
g.add_edge('Bob', 'Dave', contact=1)
42+
43+
# ================================ CONFIGURE ===================================
44+
pr.settings.verbose = True
45+
pr.settings.atom_trace = True # Enable atom trace for full explainability
46+
pr.settings.inconsistency_check = True # Enable inconsistency detection (default)
47+
48+
# ================================ LOAD GRAPH ==================================
49+
pr.load_graph(g)
50+
51+
# Declare sick and healthy as inconsistent predicates
52+
# When one is set, PyReason automatically gives the other the negated bound
53+
pr.add_inconsistent_predicate('sick', 'healthy')
54+
55+
# Declare close_contact and no_contact as inconsistent edge predicates
56+
pr.add_inconsistent_predicate('close_contact', 'no_contact')
57+
58+
# ================================ ADD RULES ===================================
59+
# If someone is sick and in contact with another person, that person may get sick
60+
pr.add_rule(pr.Rule('sick(y):[0.5,0.7] <- sick(x):[0.5,1.0], contact(x,y)', 'spread_rule'))
61+
62+
# Rule that infers no_contact on an edge when both people are sick -- will conflict
63+
# with the close_contact fact on (Alice, Bob) via the IPL
64+
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'))
65+
66+
# Rule that infers low trust on an edge when someone is sick -- will conflict
67+
# with the high trust fact on (Bob, Carol) via same-predicate conflicting bounds
68+
pr.add_rule(pr.Rule('trust(x,y):[0.0,0.2] <- sick(x):[0.5,1.0], contact(x,y)', 'distrust_rule'))
69+
70+
# Rule that infers risk on an edge (no conflict -- clean edge rule for comparison)
71+
pr.add_rule(pr.Rule('risk(x,y):[0.6,0.8] <- sick(x):[0.5,1.0], contact(x,y)', 'risk_rule'))
72+
73+
# ================================ ADD FACTS ===================================
74+
# Fact 1: Alice is sick with high confidence
75+
pr.add_fact(pr.Fact('sick(Alice):[0.8,1.0]', 'alice_sick_fact', 0, 0))
76+
77+
# Fact 2: Alice is also healthy with high confidence -- this CONTRADICTS Fact 1
78+
# Since sick and healthy are in the IPL, this creates an inconsistency
79+
pr.add_fact(pr.Fact('healthy(Alice):[0.9,1.0]', 'alice_healthy_fact', 0, 0))
80+
81+
# Fact 3: Bob is sick (no contradiction here, normal reasoning)
82+
pr.add_fact(pr.Fact('sick(Bob):[0.6,0.8]', 'bob_sick_fact', 0, 0))
83+
# Fact 3.5 : Carol is Healthy (will trigger contradiction later)
84+
pr.add_fact(pr.Fact('healthy(Carol):[0.9,1.0]', 'carol_healthy_fact', 0, 0))
85+
86+
# Fact 4 & 5: Bob is tired with two conflicting, non-overlapping bounds
87+
# Since "tired" is NOT in the IPL, this triggers a same-predicate inconsistency
88+
pr.add_fact(pr.Fact('tired(Bob):[0.8,1.0]', 'bob_tired_fact_1', 0, 0))
89+
pr.add_fact(pr.Fact('tired(Bob):[0.0,0.1]', 'bob_tired_fact_2', 0, 0))
90+
91+
# Dave has no conflicting predicates -- spread_rule will cleanly set sick(Dave)
92+
# This provides a normal node rule trace entry for comparison
93+
94+
# ---- Edge inconsistencies (set up initial state for rule-triggered conflicts) ----
95+
# Fact 6: Set close_contact on (Alice, Bob) -- quarantine_rule will later infer
96+
# no_contact on the same edge, triggering an IPL-based edge inconsistency
97+
pr.add_fact(pr.Fact('close_contact(Alice,Bob):[0.8,1.0]', 'alice_bob_close_fact', 0, 0))
98+
99+
# Fact 7: Set high trust on (Bob, Carol) -- distrust_rule will later infer
100+
# trust:[0.0,0.2] on the same edge, triggering a same-predicate edge inconsistency
101+
pr.add_fact(pr.Fact('trust(Bob,Carol):[0.9,1.0]', 'bob_carol_trust_high', 0, 0))
102+
103+
# ================================ REASON ======================================
104+
print('=' * 60)
105+
print('Running PyReason with inconsistency detection...')
106+
print('=' * 60)
107+
interpretation = pr.reason(timesteps=2)
108+
109+
# ================================ VIEW RESULTS ================================
110+
print('\n' + '=' * 60)
111+
print('Node Interpretation Changes (sick)')
112+
print('=' * 60)
113+
dataframes = pr.filter_and_sort_nodes(interpretation, ['sick'])
114+
for t, df in enumerate(dataframes):
115+
print(f'\nTIMESTEP {t}:')
116+
print(df)
117+
118+
print('\n' + '=' * 60)
119+
print('Node Interpretation Changes (healthy)')
120+
print('=' * 60)
121+
dataframes = pr.filter_and_sort_nodes(interpretation, ['healthy'])
122+
for t, df in enumerate(dataframes):
123+
print(f'\nTIMESTEP {t}:')
124+
print(df)
125+
126+
# ================================ VIEW TRACE ==================================
127+
print('\n' + '=' * 60)
128+
print('Rule Trace (shows inconsistency resolution details)')
129+
print('=' * 60)
130+
node_trace, edge_trace = pr.get_rule_trace(interpretation)
131+
print('\nNode trace:')
132+
print(node_trace.to_string())
133+
134+
if not edge_trace.empty:
135+
print('\nEdge trace:')
136+
print(edge_trace.to_string())
137+
138+
# Save the rule trace to a file for further inspection
139+
pr.save_rule_trace(interpretation)
140+
print('\nRule trace saved to current directory.')

0 commit comments

Comments
 (0)