Repository: isi-vista/adam

Issue:
Handle core vocabulary verb: Move
Move (~110): NP (~50), bare (~50), sometimes with PP/ADVP

Move: V NP (PP/ADVP)

Comments:

Patch:
diff --git a/adam/curriculum/phase1_curriculum.py b/adam/curriculum/phase1_curriculum.py
index daa19537..49138e7d 100644
--- a/adam/curriculum/phase1_curriculum.py
+++ b/adam/curriculum/phase1_curriculum.py
@@ -63,6 +63,11 @@ from adam.ontology.phase1_ontology import (
     is_recognized_particular,
     on,
     strictly_above,
+    TAKE,
+    SELF_MOVING,
+    MOVE,
+    MOVE_GOAL,
+    contacts,
 )
 from adam.ontology.phase1_spatial_relations import (
     AWAY_FROM,
@@ -72,6 +77,7 @@ from adam.ontology.phase1_spatial_relations import (
     Region,
     SpatialPath,
     TOWARD,
+    PROXIMAL,
 )
 from adam.perception.developmental_primitive_perception import (
     DevelopmentalPrimitivePerceptionFrame,
@@ -892,6 +898,109 @@ def _make_sit_curriculum():
     )
 
 
+def _make_take_curriculum():
+    taker = object_variable("taker_0", required_properties=[ANIMATE])
+    object_taken = object_variable(
+        "object_taken_0",
+        required_properties=[INANIMATE],
+        banned_properties=[IS_BODY_PART],
+    )
+
+    # X puts Y on Z
+    take_template = Phase1SituationTemplate(
+        "take",
+        salient_object_variables=[taker, object_taken],
+        actions=[
+            Action(
+                TAKE, argument_roles_to_fillers=[(AGENT, taker), (THEME, object_taken)]
+            )
+        ],
+        constraining_relations=[Relation(BIGGER_THAN, taker, object_taken)],
+    )
+
+    return _phase1_instances(
+        "taking",
+        chain(
+            *[
+                sampled(
+                    take_template,
+                    max_to_sample=25,
+                    chooser=_CHOOSER,
+                    ontology=GAILA_PHASE_1_ONTOLOGY,
+                )
+            ]
+        ),
+    )
+
+
+def _make_move_curriculum():
+    self_mover_0 = object_variable(
+        "self-mover_0", THING, required_properties=[SELF_MOVING]
+    )
+
+    other_mover_0 = object_variable("mover_0", THING, required_properties=[ANIMATE])
+    movee_0 = object_variable(
+        "movee_0",
+        THING,
+        required_properties=[INANIMATE],
+        banned_properties=[IS_BODY_PART],
+    )
+    move_goal_reference = object_variable(
+        "move-goal-reference",
+        THING,
+        required_properties=[INANIMATE],
+        banned_properties=[IS_BODY_PART],
+    )
+
+    # since we lack other prepositions at the moment,
+    # all movement has the goal of being near an arbitrary inanimate object
+    aux_variable_bindings = [(MOVE_GOAL, Region(move_goal_reference, distance=PROXIMAL))]
+
+    # bare move (e.g. "a box moves") is about half of uses in child speed
+    bare_move_template = Phase1SituationTemplate(
+        "bare-move",
+        salient_object_variables=[self_mover_0],
+        actions=[
+            Action(
+                MOVE,
+                argument_roles_to_fillers=[(AGENT, self_mover_0)],
+                auxiliary_variable_bindings=aux_variable_bindings,
+            )
+        ],
+    )
+
+    transitive_move_template = Phase1SituationTemplate(
+        "transitive-move",
+        salient_object_variables=[other_mover_0, movee_0],
+        actions=[
+            Action(
+                MOVE,
+                argument_roles_to_fillers=[(AGENT, other_mover_0), (THEME, movee_0)],
+                during=DuringAction(continuously=[contacts(other_mover_0, movee_0)]),
+                auxiliary_variable_bindings=aux_variable_bindings,
+            )
+        ],
+        constraining_relations=[bigger_than(other_mover_0, movee_0)],
+    )
+
+    # TODO: version with explicit goal
+
+    return _phase1_instances(
+        "move",
+        chain(
+            *[
+                sampled(
+                    situation,
+                    max_to_sample=25,
+                    chooser=_CHOOSER,
+                    ontology=GAILA_PHASE_1_ONTOLOGY,
+                )
+                for situation in (bare_move_template, transitive_move_template)
+            ]
+        ),
+    )
+
+
 GAILA_PHASE_1_CURRICULUM = [
     EACH_OBJECT_BY_ITSELF_SUB_CURRICULUM,
     OBJECTS_WITH_COLORS_SUB_CURRICULUM,
@@ -910,6 +1019,8 @@ GAILA_PHASE_1_CURRICULUM = [
     _make_sit_curriculum(),
     _make_put_curriculum(),
     _make_eat_curriculum(),
+    _make_take_curriculum(),
+    _make_move_curriculum(),
 ]
 """
 One particular instantiation of the curriculum for GAILA Phase 1.
diff --git a/adam/language_specific/english/english_language_generator.py b/adam/language_specific/english/english_language_generator.py
index 380b8f5e..43cae37f 100644
--- a/adam/language_specific/english/english_language_generator.py
+++ b/adam/language_specific/english/english_language_generator.py
@@ -176,18 +176,24 @@ class SimpleRuleBasedEnglishLanguageGenerator(
                     if not self._only_translate_if_referenced(object_):
                         self._noun_for_object(object_)
 
-                # We only translate those relations the user specifically calls out,
-                # not the many "background" relations which are also true.
-                for persisting_relation in self.situation.always_relations:
-                    self._translate_relation(persisting_relation)
-
                 if len(self.situation.actions) > 1:
                     raise RuntimeError(
                         "Currently only situations with 0 or 1 actions are supported"
                     )
 
+                action: Optional[Action[OntologyNode, SituationObject]]
                 if self.situation.actions:
-                    self._translate_action_to_verb(only(self.situation.actions))
+                    action = only(self.situation.actions)
+                else:
+                    action = None
+
+                # We only translate those relations the user specifically calls out,
+                # not the many "background" relations which are also true.
+                for persisting_relation in self.situation.always_relations:
+                    self._translate_relation(action, persisting_relation)
+
+                if action:
+                    self._translate_action_to_verb(action)
 
             return immutableset(
                 [
@@ -346,7 +352,11 @@ class SimpleRuleBasedEnglishLanguageGenerator(
                     determiner_node, noun_dependency_node, role=determiner_role
                 )
 
-        def _translate_relation(self, relation: Relation[SituationObject]) -> None:
+        def _translate_relation(
+            self,
+            action: Optional[Action[OntologyNode, SituationObject]],
+            relation: Relation[SituationObject],
+        ) -> None:
             if relation.relation_type == HAS:
                 # 'has' is a special case.
                 if self.situation.is_dynamic:
@@ -356,7 +366,9 @@ class SimpleRuleBasedEnglishLanguageGenerator(
                     # otherwise, we realize it as the verb "has"
                     self._translate_relation_to_verb(relation)
             elif relation.relation_type == IN_REGION:
-                prepositional_modifier = self.relation_to_prepositional_modifier(relation)
+                prepositional_modifier = self.relation_to_prepositional_modifier(
+                    action, relation
+                )
                 if prepositional_modifier:
                     self.dependency_graph.add_edge(
                         prepositional_modifier,
@@ -624,7 +636,12 @@ class SimpleRuleBasedEnglishLanguageGenerator(
 
             return modifiers
 
-        def _translate_relation_to_action_modifier(self, action, relation, modifiers):
+        def _translate_relation_to_action_modifier(
+            self,
+            action: Action[OntologyNode, SituationObject],
+            relation: Relation[SituationObject],
+            modifiers,
+        ):
             if relation.relation_type == IN_REGION:
                 # the thing the relation is predicated of must be something plausibly
                 # moving, which for now is either..
@@ -643,17 +660,13 @@ class SimpleRuleBasedEnglishLanguageGenerator(
                 )
                 if fills_legal_argument_role:
                     prepositional_modifier = self.relation_to_prepositional_modifier(
-                        relation
+                        action, relation
                     )
                     if prepositional_modifier:
                         modifiers.append((OBLIQUE_NOMINAL, prepositional_modifier))
                 else:
-                    raise RuntimeError(
-                        f"To translate a spatial relation as a verbal "
-                        f"modifier, it must either be the theme or, if "
-                        f"it is another filler, the theme must be absent:"
-                        f" {relation} in {action} "
-                    )
+                    # we don't want to translate relations of the agent (yet)
+                    return
             else:
                 raise RuntimeError(
                     f"Currently only know how to translate IN_REGION "
@@ -662,9 +675,32 @@ class SimpleRuleBasedEnglishLanguageGenerator(
                 )
 
         def relation_to_prepositional_modifier(
-            self, relation
+            self,
+            action: Optional[Action[OntologyNode, SituationObject]],
+            relation: Relation[SituationObject],
         ) -> Optional[DependencyTreeToken]:
             region = cast(Region[SituationObject], relation.second_slot)
+            # don't talk about relations to non-salient objects
+            if region.reference_object not in self.situation.salient_objects:
+                return None
+
+            if action:
+                # If both arguments of the relation are core argument roles,
+                # we assume the verb takes care of expressing their relationship.
+                core_argument_fillers = immutableset(
+                    chain(
+                        action.argument_roles_to_fillers[AGENT],
+                        action.argument_roles_to_fillers[PATIENT],
+                        action.argument_roles_to_fillers[THEME],
+                    )
+                )
+
+                if (
+                    relation.first_slot in core_argument_fillers
+                    and region.reference_object in core_argument_fillers
+                ):
+                    return None
+
             if (
                 region.direction
                 and region.direction.relative_to_axis == GRAVITATIONAL_AXIS
diff --git a/adam/ontology/phase1_ontology.py b/adam/ontology/phase1_ontology.py
index 78afff65..bb88dc7e 100644
--- a/adam/ontology/phase1_ontology.py
+++ b/adam/ontology/phase1_ontology.py
@@ -1088,7 +1088,14 @@ _TAKE_ACTION_DESCRIPTION = ActionDescription(
         partOf(_TAKE_MANIPULATOR, _TAKE_AGENT),
     ],
     preconditions=[negate(has(_TAKE_AGENT, _TAKE_THEME))],
-    postconditions=[has(_TAKE_AGENT, _TAKE_THEME)],
+    postconditions=[
+        has(_TAKE_AGENT, _TAKE_THEME),
+        Relation(
+            IN_REGION,
+            _TAKE_THEME,
+            Region(_TAKE_MANIPULATOR, distance=EXTERIOR_BUT_IN_CONTACT),
+        ),
+    ],
     asserted_properties=[
         (_TAKE_AGENT, VOLITIONALLY_INVOLVED),
         (_TAKE_AGENT, CAUSES_CHANGE),
@@ -1281,22 +1288,47 @@ _THROW_ACTION_DESCRIPTION = ActionDescription(
 
 _MOVE_AGENT = SituationObject(THING, properties=[ANIMATE])
 _MOVE_THEME = SituationObject(THING)
-_MOVE_GOAL = SituationObject(THING)
+MOVE_GOAL = SituationObject(THING)
 _MOVE_MANIPULATOR = SituationObject(THING, properties=[CAN_MANIPULATE_OBJECTS])
 
-# TODO: a proper treatment of move awaits full treatment of multiple sub-categorization frames
-_MOVE_ACTION_DESCRIPTION = ActionDescription(
-    frame=ActionDescriptionFrame(
-        {AGENT: _MOVE_AGENT, THEME: _MOVE_THEME, GOAL: _MOVE_GOAL}
-    ),
-    preconditions=[],
-    postconditions=[],
-    asserted_properties=[
-        (_MOVE_AGENT, VOLITIONALLY_INVOLVED),
-        (_MOVE_AGENT, CAUSES_CHANGE),
-        (_MOVE_THEME, UNDERGOES_CHANGE),
-    ],
-)
+
+def _make_move_descriptions() -> Iterable[Tuple[OntologyNode, ActionDescription]]:
+    # bare move - "X moves (of its own accord)"
+    yield MOVE, ActionDescription(
+        frame=ActionDescriptionFrame({AGENT: _MOVE_AGENT}),
+        postconditions=[Relation(IN_REGION, _MOVE_AGENT, MOVE_GOAL)],
+        asserted_properties=[
+            (_MOVE_AGENT, VOLITIONALLY_INVOLVED),
+            (_MOVE_AGENT, CAUSES_CHANGE),
+            (_MOVE_AGENT, UNDERGOES_CHANGE),
+        ],
+    )
+
+    # X moves Y
+    yield MOVE, ActionDescription(
+        frame=ActionDescriptionFrame({AGENT: _MOVE_AGENT, THEME: _MOVE_THEME}),
+        postconditions=[Relation(IN_REGION, _MOVE_THEME, MOVE_GOAL)],
+        asserted_properties=[
+            (_MOVE_AGENT, VOLITIONALLY_INVOLVED),
+            (_MOVE_AGENT, CAUSES_CHANGE),
+            (_MOVE_THEME, UNDERGOES_CHANGE),
+        ],
+    )
+
+    # X moves Y to Z
+    # TODO: manipulator
+    yield MOVE, ActionDescription(
+        frame=ActionDescriptionFrame(
+            {AGENT: _MOVE_AGENT, THEME: _MOVE_THEME, GOAL: MOVE_GOAL}
+        ),
+        postconditions=[Relation(IN_REGION, _MOVE_THEME, MOVE_GOAL)],
+        asserted_properties=[
+            (_MOVE_AGENT, VOLITIONALLY_INVOLVED),
+            (_MOVE_AGENT, CAUSES_CHANGE),
+            (_MOVE_THEME, UNDERGOES_CHANGE),
+        ],
+    )
+
 
 JUMP_INITIAL_SUPPORTER_AUX = SituationObject(THING)
 
@@ -1416,7 +1448,6 @@ _ACTIONS_TO_DESCRIPTIONS = [
     (TURN, _TURN_ACTION_DESCRIPTION),
     (FALL, _FALL_ACTION_DESCRIPTION),
     (THROW, _THROW_ACTION_DESCRIPTION),
-    (MOVE, _MOVE_ACTION_DESCRIPTION),
     (FLY, _FLY_ACTION_DESCRIPTION),
 ]
 
@@ -1424,6 +1455,7 @@ _ACTIONS_TO_DESCRIPTIONS.extend(_make_roll_description())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_jump_description())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_drink_description())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_sit_action_descriptions())
+_ACTIONS_TO_DESCRIPTIONS.extend(_make_move_descriptions())
 
 GAILA_PHASE_1_ONTOLOGY = Ontology(
     "gaila-phase-1",


Test patch:
diff --git a/tests/curriculum/phase1_curriculum_test.py b/tests/curriculum/phase1_curriculum_test.py
index 6a775b19..a755b1e8 100644
--- a/tests/curriculum/phase1_curriculum_test.py
+++ b/tests/curriculum/phase1_curriculum_test.py
@@ -10,6 +10,8 @@ from adam.curriculum.phase1_curriculum import (
     _make_transfer_of_possession_curriculum,
     _make_sit_curriculum,
     _make_put_curriculum,
+    _make_take_curriculum,
+    _make_move_curriculum,
 )
 
 
@@ -60,3 +62,11 @@ def test_sit():
 
 def test_put():
     _test_curriculum(_make_put_curriculum())
+
+
+def test_take():
+    _test_curriculum(_make_take_curriculum())
+
+
+def test_move():
+    _test_curriculum(_make_move_curriculum())