Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Use of PyReason for probabilistic logic #59

Open
f-bdolan opened this issue Aug 19, 2024 · 9 comments
Open

Question: Use of PyReason for probabilistic logic #59

f-bdolan opened this issue Aug 19, 2024 · 9 comments

Comments

@f-bdolan
Copy link

Is is possible to use PyR for problems in probabilistic logic? An example from Scallop is here

E is the event of an earthquake, P(A) = 0.01
'B' is the event of a burglary, P(B) = 0.12

What is P(A \/ B)?

@dyumanaditya
Copy link
Contributor

Not intrinsically, but you could however model it such that you design the probability rules. For example (I'm assuming independent events for simplicity) if:

P(A) = 0.01
and
P(B) = 0.12

You could design a rule of the form:

union_probability(A, B) : probability_func <- P(A) : [0, 1], P(B) : [0, 1]

Where probability_func is an "Annotation Function" that is user defined and can be used to give annotations to the head of the rule if the body is true. It has access to the bounds of the body clauses and has this form:

@numba.njit
def probability_func(annotations, weights):
    prob_A = annotations[0][0].lower
    prob_B = annotations[1][0].lower
    union_prob = prob_A + prob_B
    return union_prob, 1

annotations is a nested array that contains the bounds for each grounded atom for every clause. The first index is for the clause index, and the second is which grounding's bound you want to access.

You can then add this to pyreason with pr.add_annotation_function(probability_func)

NOTE: Annotation functions have to have the same function signature as above whether the parameters are used or not.

@f-bdolan
Copy link
Author

In the above syntax, how do you define the probabilities of A and B? I would expect that to look more like

union_probability(A, B) : probability_func <- P(A) : [0, 0.01], P(B) : [0, 0.12]

I admit to not understanding how those intervals affect the reasoning. All of the examples seem to include a probability of 1 for everything. Thank you!

@dyumanaditya
Copy link
Contributor

The way that you would define the probabilities are through facts. You would then use the rule mentioned above. The rule is meant to be general and take any probability defined in the facts and output the union. Therefore, the clause bounds in the rule are specified to be [0,1].

In general, it's more common to use [p, 1] where p is a probability instead of [0, p] but both can be made to work. If you want to use the latter, you would have to change the annotation function to take the .upper instead of .lower.

Footnote:
With the new version of PyReason #43, you can add a fact with:

pr.add_fact(pr.Fact('P(A) : [0.01, 1]')
pr.add_fact(pr.Fact('P(B) : [0.2, 1]')

You can try the above out by checking out that branch and doing a pip install .

@KaminiAggarwal
Copy link

KaminiAggarwal commented Aug 20, 2024

@dyumanaditya I was able to add the facts and rule as you stated above.
However, pyreason is giving attached error on below:

pr.reason(timesteps=1)

Screenshot 2024-08-20 at 12 20 00 PM

cc: @f-bdolan

@dyumanaditya
Copy link
Contributor

@KaminiAggarwal You are using Python 3.12 which is not supported, please try with 3.9. All the information for the correct setup is laid out in the readme.md

@KaminiAggarwal
Copy link

KaminiAggarwal commented Aug 21, 2024

Thanks @dyumanaditya. I was able to run the test on 3.9.
Though, I don't see the results of annotation function:

Here is the sample program:

import pyreason as pr
import numba

@numba.njit
def probability_func(annotations, weights):
    prob_A = annotations[0][0].lower
    prob_B = annotations[1][0].lower
    union_prob = prob_A + prob_B
    return union_prob, 1

pr.add_fact(pr.Fact('P(A) : [0.01, 1]'))
pr.add_fact(pr.Fact('P(B) : [0.2, 1]'))
pr.add_annotation_function(probability_func)
pr.add_rule(pr.Rule('union_probability(A, B):probability_func <- P(A):[0, 1], P(B):[0, 1]'))

interpretation = pr.reason(timesteps=1)
dataframes = pr.filter_and_sort_nodes(interpretation, ['union_probability'])
for t, df in enumerate(dataframes):
    print(df)
    print("***********")

interpretation_dict = interpretation.get_dict()

print("Interpretation dictionary from pyreason:", interpretation_dict)

Here are the results:
Converged at time: 0
Fixed Point iterations: 1
interpretation: <pyreason.scripts.interpretation.interpretation.Interpretation object at 0x10b6dc220>
Empty DataFrame
Columns: [component, union_probability]
Index: []

Interpretation dictionary from pyreason: {0: {'A': {'P': (0.01, 1.0)}, 'B': {'P': (0.2, 1.0)}}}

cc: @f-bdolan

@KaminiAggarwal
Copy link

Hi Team!

Will appreciate your help on the above.

@dyumanaditya @f-bdolan

@dyumanaditya
Copy link
Contributor

@KaminiAggarwal Please try with the following code and update your installation with the latest commit on #43

You need to set infer_edges=True to the rule since there is no graph and a pre-existing edge does not exist between A and B. You also need to pr.settings.allow_ground_rules = True, which allows you to directly access nodes in your rule instead of A and B being variables.

It may also be a good idea to round in your annotation function to prevent numerical isues.

import pyreason as pr
import numba
import numpy as np

@numba.njit
def probability_func(annotations, weights):
    print(annotations)
    prob_A = annotations[0][0].lower
    prob_B = annotations[1][0].lower
    union_prob = prob_A + prob_B
    union_prob = np.round(union_prob, 3)
    return union_prob, 1

pr.settings.allow_ground_rules = True

pr.add_fact(pr.Fact('P(A) : [0.01, 1]'))
pr.add_fact(pr.Fact('P(B) : [0.2, 1]'))
pr.add_annotation_function(probability_func)
pr.add_rule(pr.Rule('union_probability(A, B):probability_func <- P(A):[0, 1], P(B):[0, 1]', infer_edges=True))

interpretation = pr.reason(timesteps=1)
dataframes = pr.filter_and_sort_edges(interpretation, ['union_probability'])
for t, df in enumerate(dataframes):
    print(df)
    print("***********")

interpretation_dict = interpretation.get_dict()


print("Interpretation dictionary from pyreason:", interpretation_dict)

@KaminiAggarwal
Copy link

@dyumanaditya Thanks a lot! With the latest code and suggested changes, I can see the union probability of two events now:

Timestep: 0

Converged at time: 0
Fixed Point iterations: 2
Empty DataFrame
Columns: [component, union_probability]
Index: []
***********
Interpretation dictionary from pyreason: {0: {'A': {'P': (0.01, 1.0)}, 'B': {'P': (0.2, 1.0)}, ('A', 'B'): {'union_probability': (0.21, 1.0)}}}

cc: @f-bdolan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants