Skip to content

Commit d43537f

Browse files
authored
Merge pull request #42 from AbdelrhmanBassiouny/main
WorldReasoner
2 parents c7bb61d + 8398331 commit d43537f

23 files changed

Lines changed: 602 additions & 291 deletions

doc/_toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ parts:
77
- caption: Concepts
88
chapters:
99
- file: kinematic_world
10+
- file: views
11+
- file: world_reasoner
1012
- file: style_guide
1113
- caption: Examples
1214
chapters:

doc/images/accept_rule.png

5.91 KB
Loading
25.9 KB
Loading
69.5 KB
Loading

doc/intro.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,28 @@ those elements is added by specifying a {py:class}`semantic_world.world_entity.C
2727
All those things have to be added to the world for full functionality.
2828
More information on the kinematic world model can be found [here](kinematic_world.md).
2929

30+
31+
## WorldReasoner
32+
33+
The world reasoner {py:class}`semantic_world.reasoner.WorldReasoner` is a class that uses [Ripple Down Rules](https://github.com/AbdelrhmanBassiouny/ripple_down_rules/tree/main)
34+
to classify concepts and attributes of the world. This is done using a rule based classifier that benefits from incremental
35+
rule addition through querying the system and answering the prompts that pop up using python code.
36+
37+
The benefit of that is the rules of the reasoner are based on the world datastructures and are updates as the datastructures
38+
are updated. Thus, the rules become a part of the semantic world repository and are update, migrated, and versioned with it.
39+
40+
More information about the world reasoner can be found [here](world_reasoner.md).
41+
3042
## Views
3143

32-
Views are aggregation objects for bodies and connections in the world.
33-
Semantically, a `semantic_world.world_entity.View` is an interpretation of a set of links and connections.
34-
For instance, a Drawer can be seen as a view on a handle and a container that is connected via a fixed connection
44+
A View ({py:class}`semantic_world.world_entity.View`) is a different representation for a part or a collection of parts in the world that has a semantic meaning and
45+
functional purpose in specific contexts.
46+
47+
For example, a Drawer can be seen as a view on a handle and a container that is connected via a fixed connection
3548
and where the container has some prismatic connection.
3649

37-
Views can be inferred by specifying rules that make up a view. TODO Bass stuff
50+
Views can be inferred by specifying rules that make up a view. More information on how the views are inferred and used
51+
can be found [here](views.md).
3852

3953
## Database
4054

doc/views.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Views
2+
3+
A View ({py:class}`semantic_world.world_entity.View`) is a different representation for a part or a collection of parts in the world that has a semantic meaning and
4+
functional purpose in specific contexts.
5+
6+
For example, a Drawer can be seen as a view on a handle and a container that is connected via a fixed connection
7+
and where the container has some prismatic connection.
8+
9+
Views can be inferred automatically by specifying rules that make up a view.
10+
11+
## How to use the views
12+
13+
Views and any other attribute of the world that can be inferred or should be inferred through reasoning can be used
14+
through the world reasoner, you can check how to use the world reasoner [here](world_reasoner.md).
15+
16+
Some helper methods exist in the world reasoner just for the views like {py:func}`semantic_world.reasoner.WorldReasoner.infer_views`
17+
and {py:func}`semantic_world.reasoner.WorldReasoner.fit_views`.
18+

doc/world_reasoner.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# World Reasoner
2+
3+
The world reasoner {py:class}`semantic_world.reasoner.WorldReasoner` is a class that uses [Ripple Down Rules](https://github.com/AbdelrhmanBassiouny/ripple_down_rules/tree/main)
4+
to classify concepts and attributes of the world. This is done using a rule based classifier that benefits from incremental
5+
rule addition through querying the system and answering the prompts that pop up using python code.
6+
7+
The benefit of that is the rules of the reasoner are based on the world datastructures and are updates as the datastructures
8+
are updated. Thus, the rules become a part of the semantic world repository and are update, migrated, and versioned with it.
9+
10+
## How to use:
11+
12+
There are two ways in which the reasoner can be used, classification mode, and fitting mode, both of which are explained
13+
bellow.
14+
15+
### A: Classification Mode
16+
17+
In classification mode, the reasoner is used as is with it's latest knowledge or rule trees to classify concepts about the
18+
world.
19+
20+
For example lets say the reasoner now has rules that enable it find specific types of views like the Drawer and the Cabinet.
21+
The way to use the reasoner is like the following example:
22+
23+
```python
24+
from semantic_world.reasoner import WorldReasoner
25+
from semantic_world.adapters.urdf import URDFParser
26+
27+
def create_kitchen_world(kitchen_path: str = 'kitchen-small.urdf'):
28+
return URDFParser(kitchen_path)
29+
30+
kitchen_world = create_kitchen_world()
31+
reasoner = WorldReasoner(kitchen_world)
32+
found_concepts = reasoner.reason()
33+
34+
# 1st method, access the views directly from the reasoning result
35+
new_views = found_concepts['views']
36+
print(new_views)
37+
38+
# Or 2nd method, access all the views from the world.views, but this will include all views not just the new ones.
39+
all_views = kitchen_world.views
40+
print(all_views)
41+
```
42+
43+
Similarly, for any other world attribute that the reasoner can infer values for, just replace the 'views' with the
44+
appropriate attribute name.
45+
46+
### B: Fitting Mode
47+
48+
In fitting mode, the reasoner can be used to improve and enlarge it's rule tree or even to widen it's application to even
49+
more attributes of the world.
50+
51+
For example, let's say you want to improve an existing rule that classifies Drawers, you can do that as follows:
52+
53+
```python
54+
from semantic_world.reasoner import WorldReasoner
55+
from semantic_world.adapters.urdf import URDFParser
56+
from semantic_world.views.views import Drawer
57+
58+
def create_kitchen_world(kitchen_path: str = 'kitchen-small.urdf'):
59+
return URDFParser(kitchen_path)
60+
61+
kitchen_world = create_kitchen_world()
62+
reasoner = WorldReasoner(kitchen_world)
63+
64+
reasoner.fit_attribute("views", [Drawer], update_existing_views=True, world_factory=create_kitchen_world)
65+
```
66+
67+
Then you will be prompted to write a rule for Drawer, and you can see the currently detected drawers shown in the Ipyton
68+
shell. Maybe you see a mistake and not all the currently detected drawers are actual drawers, so you want to filter the
69+
results. To start writing your rule, just type `%edit` in the Ipython terminal as shown the image bellow, or if using
70+
the GUI just press the `Edit` button.
71+
72+
<img src="images/write_edit_in_ipython.png" alt="Write %edit in Ipython" width="600"/>
73+
74+
Now, a template file with some imports and an empty function is openned for you to write your rule inside the body of
75+
the function as shown bellow:
76+
77+
```python
78+
from dataclasses import dataclass, field
79+
from posixpath import dirname
80+
from typing_extensions import Any, Callable, ClassVar, Dict, List, Optional, Type, Union
81+
from ripple_down_rules.rdr import GeneralRDR
82+
from ripple_down_rules.datastructures.dataclasses import CaseQuery
83+
from semantic_world.world_entity import View
84+
from semantic_world.reasoner import WorldReasoner
85+
from semantic_world.world import World
86+
from semantic_world.views.views import Drawer
87+
88+
def world_views_of_type_drawer(case: World) -> List[Drawer]:
89+
"""Get possible value(s) for World.views of type Drawer."""
90+
# Write code here
91+
pass
92+
```
93+
94+
You can write a filter on the current views of type Drawer as follows:
95+
96+
```python
97+
from dataclasses import dataclass, field
98+
from posixpath import dirname
99+
from typing_extensions import Any, Callable, ClassVar, Dict, List, Optional, Type, Union
100+
from ripple_down_rules.rdr import GeneralRDR
101+
from ripple_down_rules.datastructures.dataclasses import CaseQuery
102+
from semantic_world.world_entity import View
103+
from semantic_world.reasoner import WorldReasoner
104+
from semantic_world.world import World
105+
from semantic_world.views.views import Drawer
106+
107+
def world_views_of_type_drawer(case: World) -> List[Drawer]:
108+
"""Get possible value(s) for World.views of type Drawer."""
109+
known_drawers = [v for v in case.views if isinstance(v, Drawer)]
110+
good_drawers = [d for d in known_drawers if d.name.name != "bad_drawer"]
111+
return good_drawers
112+
```
113+
114+
So the above is the generated template, and I just filled in the body of the function with my rule logic. After that
115+
you write `%load` in the Ipython and the function you just wrote will be available to you to test it out in the Ipython
116+
shell as shown bellow (in the GUI just pres the Load button):
117+
118+
<img src="images/load_rule_and_test_it.png" alt="Load Rule in Ipython" width="1200"/>
119+
120+
Then if you want to change the rule, just edit the already open template file and do `load` again. Once you are happy
121+
with your rule results just return the function output as follows (in the GUI just press the Accept button):
122+
123+
<img src="images/accept_rule.png" alt="Accept Rule in Ipython" width="400"/>
124+
125+
If you also want to contribute to the semantic world package, then it's better to do that in the `test_views/test_views.py`
126+
test file. Since there is already rules for Drawer, there would already be a test method for that. All you need to do is
127+
set the `update_existing_views` to `True` like this:
128+
129+
```python
130+
def test_drawer_view(self):
131+
self.fit_rules_for_a_view_in_apartment(Drawer, scenario=self.test_drawer_view, update_existing_views=True)
132+
```
133+
then run the test from the terminal using `pytest` as follows:
134+
```bash
135+
cd semantic_world/test/test_views && pytest -k "test_drawer_view"
136+
```
137+
Then answer the prompt with the rule as described before. Now the rules for the Drawer view has been updated, Nice Work!
138+
139+
You could also create a new test method if your world is not the apartment or if you want to add a specific test for a
140+
specific context, more tests are always welcome :D. Just make sure you set the scenario to be the new test method name,
141+
and set the world factory to the method that creates your world (if it doesn't exist create one and put it in the test
142+
file).
143+
144+
In addition you can fit the reasoner on a totaly new concept/attribute of the world instead of `views`, maybe `regions`
145+
or `predicates` , ...etc. What's great is that inside your rules you can use the views that were classified already by
146+
the views rules, and vice verse, you can add views rules that use outputs from rules on other attributes as well.

examples/fit_reasoner.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from semantic_world.reasoner import WorldReasoner
2+
from semantic_world.adapters.urdf import URDFParser
3+
from semantic_world.views.views import Drawer
4+
5+
6+
def create_kitchen_world(kitchen_path: str = '../resources/urdf/kitchen-small.urdf'):
7+
return URDFParser(kitchen_path).parse()
8+
9+
10+
kitchen_world = create_kitchen_world()
11+
reasoner = WorldReasoner(kitchen_world)
12+
13+
reasoner.fit_views([Drawer], update_existing_views=True, world_factory=create_kitchen_world)

src/semantic_world/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
__version__ = "0.0.2"
1+
__version__ = "0.0.2"
2+
3+
4+
import logging
5+
6+
7+
logger = logging.Logger("semantic_world")
8+
logger.setLevel(logging.INFO)

src/semantic_world/exceptions.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
1-
class WorldException(Exception):
2-
pass
1+
from __future__ import annotations
2+
from typing_extensions import List, TYPE_CHECKING
33

4+
if TYPE_CHECKING:
5+
from semantic_world.world_entity import View
46

5-
class CorruptShapeException(WorldException):
6-
pass
77

8+
class LogicalError(Exception):
9+
"""
10+
An error that happens due to mistake in the logical operation or usage of the API during runtime.
11+
"""
12+
...
813

9-
class CorruptMeshException(CorruptShapeException):
10-
pass
1114

15+
class UsageError(LogicalError):
16+
"""
17+
An exception raised when an incorrect usage of the API is encountered.
18+
"""
19+
...
1220

13-
class CorruptURDFException(CorruptShapeException):
14-
pass
1521

22+
class AddingAnExistingViewError(UsageError):
23+
def __init__(self, view: View):
24+
msg = f'View {view} already exists.'
25+
super().__init__(msg)
1626

17-
class TransformException(WorldException):
18-
pass
1927

20-
21-
class DuplicateNameException(WorldException):
22-
pass
28+
class DuplicateViewError(UsageError):
29+
def __init__(self, views: List[View]):
30+
msg = f'Views {views} are duplicates, while views elements should be unique.'
31+
super().__init__(msg)

0 commit comments

Comments
 (0)