pyreason icon indicating copy to clipboard operation
pyreason copied to clipboard

PyReason not maintaining bound variables in rule body?

Open Pugio opened this issue 1 year ago • 2 comments

I'm modeling a simple purchasing scenario with two unconnected subgraphs of seller/buyer/house and seller2/buyer2/house2. I have a rule which defines how properties are transferred, and I set up some initial facts on seller and buyer. My main rule is:

property_transfer(x, y, p) <-1 owns(x,p), sold(x,y), paidMoney(y, x), noDeedCustom(p)"

I expect that once each variable in the predicate is bound, it would stay bound throughout the full evaluation of the rule's body (through all the clauses). I expect this to mean "x owns p AND x sold to y AND y paid money to x AND p has noDeedCustom".

However when I generate a rule trace, it seems like it's matching each clause independently, so owns(x,p) is matching both seller->buyer AND seller2->buyer, and then every subsequent clause is matching only the initial seller/buyer/house... yet somehow the property_transfer edge is getting applied to the seller2/buyer2/house2 nodes.

Am I misunderstanding something fundamental about how rules are evaluated, or is this a bug? Thank you.

Code and results below:

import pyreason as pr
import networkx as nx

pr.settings.verbose = True     # Print info to screen
pr.settings.atom_trace = True  # This allows us to view all the atoms that have made a certain rule fire



# Create a graph
g = nx.DiGraph()

# --- Scenario 1 ---
g.add_node('seller', person=1)
g.add_node('buyer', person=1)
g.add_node('house', property=1, noDeedCustom=1)

g.add_edge("seller", "house", owns=1)
g.add_edge("seller", "buyer", sellingTo=1)
g.add_edge("buyer", "seller", buyingFrom=1)
g.add_edge("buyer", "house", notOwns=1)

# --- Scenario 2 ---
g.add_node('seller2', person=1)
g.add_node('buyer2', person=1)
g.add_node('house2', property=1)

g.add_edge("seller2", "house2", owns=1)
g.add_edge("seller2", "buyer2", sellingTo=1)
g.add_edge("buyer2", "seller2", buyingFrom=1)
g.add_edge("buyer2", "house2", notOwns=1)

pr.add_rule(
    pr.Rule("property_transfer(x, y, p) <-1 owns(x,p), sold(x,y), paidMoney(y, x), noDeedCustom(p)","basic_transfer")
)

pr.add_rule(
    pr.Rule("owns(x, p) <-1 property_transfer(x, y, p), property(p)","basic_owns")
)

pr.add_fact(pr.Fact('sale_fact', ('seller', 'buyer'), 'sold', [1, 1], 0, 0,))
pr.add_fact(pr.Fact('money_fact', ('buyer', 'seller'), 'paidMoney', [1, 1], 0, 0,))

interpretation = pr.reason(timesteps=1)
Time ,Fixed-Point-Operation ,Edge                    ,Label             ,Old Bound   ,New Bound   ,Occurred Due To ,Clause-1                                       ,Clause-2                ,Clause-3                ,Clause-4
   0 ,                    0 ,"('seller', 'buyer')"   ,sold              ,"[0.0,1.0]" ,"[1.0,1.0]" ,sale_fact       ,                                               ,                        ,                        ,
   0 ,                    0 ,"('buyer', 'seller')"   ,paidMoney         ,"[0.0,1.0]" ,"[1.0,1.0]" ,money_fact      ,                                               ,                        ,                        ,
   
   1 ,                    1 ,"('seller', 'house')"   ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('seller', 'buyer')"   ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('buyer', 'seller')"   ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('buyer', 'house')"    ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   
   1 ,                    1 ,"('seller2', 'house2')" ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('seller2', 'buyer2')" ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('buyer2', 'seller2')" ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']
   1 ,                    1 ,"('buyer2', 'house2')"  ,property_transfer ,"[0.0,1.0]" ,"[1.0,1.0]" ,basic_transfer  ,"[('seller', 'house'), ('seller2', 'house2')]" ,"[('seller', 'buyer')]" ,"[('buyer', 'seller')]" ,['house']

Pugio avatar Aug 13 '24 06:08 Pugio

Hello @Pugio,

PyReason only supports rules that have predicates over nodes or edges, and not over three elements. The head of your rule property_transfer(x, y, p) is therefore incorrect. Can you modify your scenario such that the head of the rule looks like head(x,y) ?

dyumanaditya avatar Aug 14 '24 08:08 dyumanaditya

Thank you. I was unable to find a complete description of the rule syntax in the documentation. I will try to reformulate the problem and check that that works. It might be useful to throw an error in the case of incorrect predicate syntax.

Pugio avatar Aug 14 '24 11:08 Pugio