Skip to content

Commit 00de233

Browse files
ColtonPayneclaude
andcommitted
Add functional tests for partial edge grounding
Two new test cases validate that ground node names in edge clauses are correctly pre-populated as groundings, enabling neighbor-based edge discovery when the exact edge pair isn't directly enumerable. - test_partial_edge_grounding: ground node in first position - test_partial_edge_grounding_reverse: ground node in second position Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 77510f1 commit 00de233

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

tests/functional/test_edge_inference.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Edge inference rule tests for PyReason
22
import pyreason as pr
3+
import networkx as nx
34
import pytest
45

56

@@ -125,3 +126,91 @@ def test_anyBurl_rule_4(mode):
125126
assert len(dataframes) == 2, 'Pyreason should run exactly 1 fixpoint operations'
126127
assert len(dataframes[1]) == 1, 'At t=1 there should be only 1 new isConnectedTo atom'
127128
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'
129+
130+
131+
@pytest.mark.slow
132+
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
133+
def test_partial_edge_grounding(mode):
134+
"""Test that ground node names in edge clauses are pre-populated as groundings.
135+
136+
When a rule has an edge clause like trusts(Alice, y) where Alice is a known
137+
node but the specific edge pair isn't enumerated, partial grounding ensures
138+
Alice is recognized and edges from Alice are discovered via neighbor lookups.
139+
"""
140+
setup_mode(mode)
141+
pr.settings.allow_ground_rules = True
142+
143+
# Build a small graph: Alice trusts Bob, Bob trusts Carol
144+
# Alice has the 'admin' attribute
145+
graph = nx.DiGraph()
146+
graph.add_node("Alice", admin=1)
147+
graph.add_node("Bob", admin=0)
148+
graph.add_node("Carol", admin=0)
149+
graph.add_edge("Alice", "Bob", trusts=1)
150+
graph.add_edge("Bob", "Carol", trusts=1)
151+
pr.load_graph(graph)
152+
153+
# Rule: if Alice is admin and Alice trusts someone, infer can_access edge
154+
# The edge clause trusts(Alice, y) uses a ground node name (Alice) + free variable (y)
155+
# Partial grounding pre-populates Alice so neighbor lookup finds the edge to Bob
156+
pr.add_rule(pr.Rule(
157+
'can_access(Alice, y) <-1 admin(Alice), trusts(Alice, y)',
158+
'partial_ground_rule',
159+
infer_edges=True,
160+
))
161+
162+
interpretation = pr.reason(timesteps=1)
163+
164+
dataframes = pr.filter_and_sort_edges(interpretation, ['can_access'])
165+
for t, df in enumerate(dataframes):
166+
print(f'TIMESTEP - {t}')
167+
print(df)
168+
print()
169+
170+
assert len(dataframes) == 2, 'Should have 2 timesteps of results'
171+
assert len(dataframes[1]) == 1, 'At t=1 there should be exactly 1 new can_access edge'
172+
assert ('Alice', 'Bob') in dataframes[1]['component'].values.tolist(), \
173+
'can_access(Alice, Bob) should be inferred'
174+
assert dataframes[1]['can_access'].iloc[0] == [1, 1], \
175+
'can_access(Alice, Bob) should have bounds [1,1]'
176+
177+
178+
@pytest.mark.slow
179+
@pytest.mark.parametrize("mode", ["regular", "fp", "parallel"])
180+
def test_partial_edge_grounding_reverse(mode):
181+
"""Test partial grounding when the ground node is in the second position of the edge clause.
182+
183+
Rule: can_reach(y, Carol) <- trusts(y, Carol), admin(Carol)
184+
Carol is ground in position 2 of the edge clause.
185+
"""
186+
setup_mode(mode)
187+
pr.settings.allow_ground_rules = True
188+
189+
graph = nx.DiGraph()
190+
graph.add_node("Alice", admin=0)
191+
graph.add_node("Bob", admin=0)
192+
graph.add_node("Carol", admin=1)
193+
graph.add_edge("Alice", "Bob", trusts=1)
194+
graph.add_edge("Bob", "Carol", trusts=1)
195+
pr.load_graph(graph)
196+
197+
pr.add_rule(pr.Rule(
198+
'can_reach(y, Carol) <-1 admin(Carol), trusts(y, Carol)',
199+
'partial_ground_reverse_rule',
200+
infer_edges=True,
201+
))
202+
203+
interpretation = pr.reason(timesteps=1)
204+
205+
dataframes = pr.filter_and_sort_edges(interpretation, ['can_reach'])
206+
for t, df in enumerate(dataframes):
207+
print(f'TIMESTEP - {t}')
208+
print(df)
209+
print()
210+
211+
assert len(dataframes) == 2, 'Should have 2 timesteps of results'
212+
assert len(dataframes[1]) == 1, 'At t=1 there should be exactly 1 new can_reach edge'
213+
assert ('Bob', 'Carol') in dataframes[1]['component'].values.tolist(), \
214+
'can_reach(Bob, Carol) should be inferred'
215+
assert dataframes[1]['can_reach'].iloc[0] == [1, 1], \
216+
'can_reach(Bob, Carol) should have bounds [1,1]'

0 commit comments

Comments
 (0)