|
| 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