Example with rule inference

Contents

Example with rule inference#

In this example, we show the how EQL allows for straight forward inference (i.e. rule-based reasoning) for classification of relational concepts.

In the previous example, we wrote an advanced query that joined multiple sources together to find the kinematic tree of a drawer. Now, we will show how to easily construct the Drawer instance(s) from the found kinematic tree(s).

Here we introduce the infer function that allows for inferring new entities from existing ones based on a set of conditions. In addition, we show how to use the rule_mode to allow for rule-based reasoning.

Example Usage#

from dataclasses import dataclass, field

from typing_extensions import List

from entity_query_language import entity, symbol, infer, From, HasType, rule_mode, let


@symbol
@dataclass
class Body:
    name: str


@dataclass
class Container(Body):
    ...


@dataclass
class Handle(Body):
    ...


@symbol
@dataclass
class Connection:
    parent: Body
    child: Body


@dataclass
class PrismaticConnection(Connection):
    ...


@dataclass
class FixedConnection(Connection):
    ...


@symbol
@dataclass
class World:
    id_: int
    bodies: List[Body]
    connections: List[Connection] = field(default_factory=list)


@symbol
@dataclass
class Drawer:
    handle: Body
    body: Body


# Create the world with its bodies and connections
world = World(1, [Container("Container1"), Container("Container2"), Handle("Handle1"), Handle("Handle2")])
c1_c2 = PrismaticConnection(world.bodies[0], world.bodies[1])
c2_h2 = FixedConnection(world.bodies[1], world.bodies[3])
world.connections = [c1_c2, c2_h2]

# A rule for finding drawers.
with rule_mode():
    # Declare the variables
    prismatic_connection = let(type_=PrismaticConnection, domain=world.connections)
    fixed_connection = let(type_=FixedConnection, domain=world.connections)
    parent_container = prismatic_connection.parent
    drawer_body = fixed_connection.parent
    handle = fixed_connection.child

    # Write the rule body
    rule = infer(entity(Drawer(handle=handle, body=drawer_body),
                        HasType(prismatic_connection.parent, Container),
                        HasType(fixed_connection.child, Handle),
                        prismatic_connection.child == fixed_connection.parent))

results = list(rule.evaluate())
assert len(results) == 1
assert results[0].body.name == "Container2"
assert results[0].handle.name == "Handle2"

The key difference between this example and the previous one is that our entity is now a Drawer instance that gets inferred from the components of the kinematic tree that is found by the query conditions.

To do that, we had to be in the rule_mode to allow for symbolic inference of the Drawer instance without invoking the Drawer constructor and without implying that we are querying for drawers. This is done by overriding the __new__ method of the Drawer class with the @symbol decorator.