Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/v3.0.0' into v3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dyumanaditya committed Oct 28, 2024
2 parents 8863526 + 597a975 commit 6220a21
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 69 deletions.
186 changes: 117 additions & 69 deletions docs/source/user_guide/pyreason_graphs.rst
Original file line number Diff line number Diff line change
@@ -1,89 +1,137 @@
PyReason Graphs
==============
**PyReason Graphs ** (Brief Intro)
PyReason supports direct reasoning over knowledge graphs. PyReason graphs have full explainability of the reasoning process. (add more)
===============

-Notes: go more indepth about use cases of Graphs, connection to Nuero symbolic reasoning, other pyreason logic concepts etc.
PyReason supports direct reasoning over knowledge graphs. PyReason graphs have full explainability of the reasoning process. Graphs serve as the knowledge base for PyReason, allowing users to create visual representations based on rules, relationships, and connections.

Methods for Loading Graphs
^^^^^^^^^^^^^^^^^^^^^^^^^^
In PyReason there are two Methods for loading graphs: Networkx and GraphMl
Methods for Creating Graphs
---------------------------
In PyReason there are two ways to create graphs: Networkx and GraphMl
Networkx allows you to manually add nodes and edges, whereas GraphMl reads in a directed graph from a file.


Networkx Example
^^^^^^^^^^^^^^^^
You can also build a graph using Networkx.
----------------
Using Networkx, you can create a **`directed <https://en.wikipedia.org/wiki/Directed_graph>`_** graph object. Users can add and remove nodes and edges from the graph.

Read more about Networkx `here <https://networkx.org/documentation/stable/reference/classes/digraph.html>`_.

The following graph represents a network of people and the pets that
they own.

1. Mary is friends with Justin
2. Mary is friends with John
3. Justin is friends with John

And

1. Mary owns a cat
2. Justin owns a cat and a dog
3. John owns a dog

.. code:: python
import networkx as nx
# ================================ CREATE GRAPH====================================
# Create a Directed graph
g = nx.DiGraph()
# Add the nodes
g.add_nodes_from(['John', 'Mary', 'Justin'])
g.add_nodes_from(['Dog', 'Cat'])
# Add the edges and their attributes. When an attribute = x which is <= 1, the annotation
# associated with it will be [x,1]. NOTE: These attributes are immutable
# Friend edges
g.add_edge('Justin', 'Mary', Friends=1)
g.add_edge('John', 'Mary', Friends=1)
g.add_edge('John', 'Justin', Friends=1)
# Pet edges
g.add_edge('Mary', 'Cat', owns=1)
g.add_edge('Justin', 'Cat', owns=1)
g.add_edge('Justin', 'Dog', owns=1)
g.add_edge('John', 'Dog', owns=1)
import networkx as nx
# ================================ CREATE GRAPH====================================
# Create a Directed graph
g = nx.DiGraph()
# Add the nodes
g.add_nodes_from(['John', 'Mary', 'Justin'])
g.add_nodes_from(['Dog', 'Cat'])
# Add the edges and their attributes. When an attribute = x which is <= 1, the annotation
# associated with it will be [x,1]. NOTE: These attributes are immutable
# Friend edges
g.add_edge('Justin', 'Mary', Friends=1)
g.add_edge('John', 'Mary', Friends=1)
g.add_edge('John', 'Justin', Friends=1)
# Pet edges
g.add_edge('Mary', 'Cat', owns=1)
g.add_edge('Justin', 'Cat', owns=1)
g.add_edge('Justin', 'Dog', owns=1)
g.add_edge('John', 'Dog', owns=1)
After the graph has been created, it can be loaded with:

.. code:: python
import pyreason as pr
pr.load_graph(graph: nx.DiGraph)
Additional Considerations:
--------------------------
Attributes to Bounds:

In Networkx, each graph, node, and edge can hold key/value attribute pairs in an associated attribute dictionary (the keys must be hashable).

In PyReason, these attributes get transformed into "bounds". The attribute value in Networkx, is translated into the lower bound in PyReason.

.. code:: python
import networkx as nx
g = nx.DiGraph()
g.add_node("some_node", attribute1=1, attribute2="0,0")
When the graph is loaded, "some_node" is given the attribute1: [1,1], and attribute2 : [0,0].

If the attribute is a simple value, it is treated as both the lower and upper bound in PyReason. If a specific pair of bounds is required (e.g., for coordinates or ranges), the value should be provided as a string in a specific format.



GraphMl Example
^^^^^^^^^^^^^^^
Using GraphMl, you can also read in from a file.
---------------
Using `GraphMl <https://en.wikipedia.org/wiki/GraphML>`_, you can read a graph in from a file.

.. code:: xml
<?xml version='1.0' encoding='utf-8'?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="owns" for="edge" attr.name="owns" attr.type="long" />
<key id="Friends" for="edge" attr.name="Friends" attr.type="long" />
<graph edgedefault="directed">
<node id="John" />
<node id="Mary" />
<node id="Justin" />
<node id="Dog" />
<node id="Cat" />
<edge source="John" target="Mary">
<data key="Friends">1</data>
</edge>
<edge source="John" target="Justin">
<data key="Friends">1</data>
</edge>
<edge source="John" target="Dog">
<data key="owns">1</data>
</edge>
<edge source="Mary" target="Cat">
<data key="owns">1</data>
</edge>
<edge source="Justin" target="Mary">
<data key="Friends">1</data>
</edge>
<edge source="Justin" target="Cat">
<data key="owns">1</data>
</edge>
<edge source="Justin" target="Dog">
<data key="owns">1</data>
</edge>
</graph>
</graphml>
<?xml version='1.0' encoding='utf-8'?>
<graphml
xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="owns" for="edge" attr.name="owns" attr.type="long" />
<key id="Friends" for="edge" attr.name="Friends" attr.type="long" />
<graph edgedefault="directed">
<node id="John" />
<node id="Mary" />
<node id="Justin" />
<node id="Dog" />
<node id="Cat" />
<edge source="John" target="Mary">
<data key="Friends">1</data>
</edge>
<edge source="John" target="Justin">
<data key="Friends">1</data>
</edge>
<edge source="John" target="Dog">
<data key="owns">1</data>
</edge>
<edge source="Mary" target="Cat">
<data key="owns">1</data>
</edge>
<edge source="Justin" target="Mary">
<data key="Friends">1</data>
</edge>
<edge source="Justin" target="Cat">
<data key="owns">1</data>
</edge>
<edge source="Justin" target="Dog">
<data key="owns">1</data>
</edge>
</graph>
</graphml>
Then load the graph using the following:

.. code:: python
import pyreason as pr
pr.load_graphml('path_to_file')
import pyreason as pr
pr.load_graphml('path_to_file')
Graph Output:

.. code:: python
.. figure:: docs/source/tutorials/basic_graph.png
:alt: image

?? Add image output of graph possibly?
96 changes: 96 additions & 0 deletions docs/source/user_guide/pyreason_rules.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
PyReason Rules
==============
- A rule is a statement that establishes a relationship between
premises and a conclusion, allowing for the derivation of the
conclusion if the premises are true. Rules are foundational to
logical systems, facilitating the inference process.

.. figure:: docs/source/tutorials/rule_image.png
:alt: image

- Every rule has a head and a body. The head determines what will
change in the graph if the body is true.

How to Create Rules
-------------------

In PyReason, rules are used to create relationships between different elements in the graph. These relationships can be used to infer new facts or make decisions based on existing graph data.


### PyReason Rule Class

To create a new **Rule** object in PyReason, use the `Rule` class with the following parameters:

1. **rule_text**: The rule in textual format (the actual rule logic).
2. **name**: A name for the rule. This name will appear in the rule trace.
3. **infer_edges**: A boolean indicating whether new edges should be inferred when the rule is applied.
4. **set_static**: A boolean indicating whether the atom in the head should be set as static after the rule is applied. This means the bounds of that atom will no longer change.
5. **immediate_rule**: A boolean indicating whether the rule is immediate. Immediate rules check for more applicable rules immediately after being applied.
6. **custom_thresholds**: A list or map of custom thresholds for the rule. If not specified, default thresholds for ANY are used. This can be a list of thresholds, or a map of clause index to threshold.




Important Notes on Rule Formating:

1. The head of the rule is always on the left hand side of the rule.
2. The body of the rule is always on the right hand side of the rule.
3. You can include timestep in the rule by using the `<-timestep` body.
4. You can include multiple bodies in the rule by using the `<-timestep body1, body2, body3`.
5. To compare two nodes, both the nodes should have an attribute in common.
1. For example using the :ref:`pyreason_graphs.rst` example, in the rule below, both the people have an attribute 'Friends' in common which is the friends in the graph.
2. So, we can compare the Friends status of both the customers to check if they are the Friends or not.

.. code-block:: python
pr.add_rule(pr.Rule('popular(x) <-1 popular(y), Friends(x,y)'))
6. To compare a particular attribute of a node with another node, you need to use the attribute like attribute "owns" is used here.
1. Note that nodes can be attributes themeselves, and thus refered to by node name
.. code-block:: python
pr.add_rule('popular(x) <-1 popular(y), Friends(x,y), owns(y,z), owns(x,z)', 'popular_rule')
More Examples
-------------

Refering to our :ref:`pyreason_graphs.rst` example, we want to create a rule to determine popularity. The rule will state that if a person has a friend who is popular *and* has the same pet as they do, then they are popular.

.. code:: text
popular(x) : [1,1] <-1 popular(y) : [1,1] , Friends(x,y) : [1,1] , owns(y,z) : [1,1] , owns(x,z) : [1,1]
The rule is read as follows:
- **Head**: `popular(x) : [1,1]`
- **Body**: `popular(y) : [1,1], Friends(x,y) : [1,1], owns(y,z) : [1,1], owns(x,z) : [1,1]`
- The **head** and **body** are separated by an arrow (`<-1`), and the rule is applied after `1` timestep.


### Adding the Rule to PyReason

1. Add the rule directly

To add the rule directly, we must specify the rule and a name for it. Here we will use "popular_rule".

.. code:: python
import pyreason as pr
pr.add_rule(pr.Rule('popular(x) <-1 popular(y), Friends(x,y), owns(y,z), owns(x,z)', 'popular_rule'))
The name helps understand which rules fired during reasoning later on.

2. Add the rule from a .txt file

To add the rule from a text file, ensure the file is in .txt format, and contains the rule in the format shown above.

.. code:: text
popular(x) <-1 popular(y), Friends(x,y), owns(y,z), owns(x,z)
Now we can load the rule from the file using the following code:

.. code:: python
import pyreason as pr
pr.add_rules_from_file('rules.txt')

0 comments on commit 6220a21

Please sign in to comment.