Repository: isi-vista/adam

Issue:
Handle core vocabulary verb: Throw
Throw (~200): PP (60), ADVP (>80), almost always with NP (~180)

Throw: V NP (PP/ADVP)

Comments:

Patch:
diff --git a/adam/curriculum/phase1_curriculum.py b/adam/curriculum/phase1_curriculum.py
index 558b8413..ad4c4e4a 100644
--- a/adam/curriculum/phase1_curriculum.py
+++ b/adam/curriculum/phase1_curriculum.py
@@ -14,7 +14,7 @@ from adam.language_specific.english.english_language_generator import (
     PREFER_DITRANSITIVE,
     USE_ADVERBIAL_PATH_MODIFIER,
 )
-from adam.ontology import THING
+from adam.ontology import THING, OntologyNode
 from adam.ontology.during import DuringAction
 from adam.ontology.ontology import Ontology
 from adam.ontology.phase1_ontology import (
@@ -74,6 +74,9 @@ from adam.ontology.phase1_ontology import (
     PUSH,
     PUSH_SURFACE_AUX,
     PUSH_GOAL,
+    THROW,
+    THROW_GOAL,
+    BOX,
 )
 from adam.ontology.phase1_spatial_relations import (
     AWAY_FROM,
@@ -103,6 +106,7 @@ from adam.situation.templates.phase1_templates import (
     color_variable,
     object_variable,
     sampled,
+    TemplateObjectVariable,
 )
 
 _CHOOSER = RandomChooser.for_seed(0)
@@ -114,6 +118,21 @@ _Phase1InstanceGroup = InstanceGroup[  # pylint:disable=invalid-name
 ]
 
 
+def _standard_object(
+    debug_handle: str,
+    root_node: OntologyNode = INANIMATE_OBJECT,
+    *,
+    required_properties: Iterable[OntologyNode] = tuple(),
+) -> TemplateObjectVariable:
+    banned_properties = [IS_BODY_PART, LIQUID]
+    return object_variable(
+        debug_handle=debug_handle,
+        root_node=root_node,
+        banned_properties=banned_properties,
+        required_properties=required_properties,
+    )
+
+
 def _phase1_instances(
     description: str, situations: Iterable[HighLevelSemanticsSituation]
 ) -> _Phase1InstanceGroup:
@@ -305,7 +324,7 @@ def _make_transfer_of_possession_curriculum() -> _Phase1InstanceGroup:
     action_variable("transfer-verb", with_properties=[TRANSFER_OF_POSSESSION])
     giver = object_variable("person_0", PERSON)
     recipient = object_variable("person_1", PERSON)
-    given_object = object_variable("give_object_0", INANIMATE_OBJECT)
+    given_object = _standard_object("give_object_0")
 
     return _phase1_instances(
         "transfer-of-possession",
@@ -600,7 +619,7 @@ def _make_roll_curriculum():
 
 
 JUMPER = object_variable("jumper_0", THING, required_properties=[CAN_JUMP])
-JUMPED_OVER = object_variable("jumped_over", THING)
+JUMPED_OVER = _standard_object("jumped_over")
 
 
 def _make_jump_curriculum():
@@ -661,22 +680,12 @@ def make_jump_over_object_template():
 
 def _make_put_curriculum():
     putter = object_variable("putter_0", required_properties=[ANIMATE])
-    object_put = object_variable(
-        "object_0", required_properties=[INANIMATE], banned_properties=[IS_BODY_PART]
-    )
+    object_put = _standard_object("object_0", required_properties=[INANIMATE])
 
-    on_region_object = object_variable(
-        "on_region_object",
-        INANIMATE_OBJECT,
-        required_properties=[CAN_HAVE_THINGS_RESTING_ON_THEM],
-        banned_properties=[IS_BODY_PART],
-    )
-    in_region_object = object_variable(
-        "in_region_object",
-        INANIMATE_OBJECT,
-        required_properties=[HOLLOW],
-        banned_properties=[IS_BODY_PART],
+    on_region_object = _standard_object(
+        "on_region_object", required_properties=[CAN_HAVE_THINGS_RESTING_ON_THEM]
     )
+    in_region_object = _standard_object("in_region_object", required_properties=[HOLLOW])
 
     # X puts Y on Z
     put_on_template = Phase1SituationTemplate(
@@ -747,9 +756,7 @@ def _make_put_curriculum():
 
 
 def _make_drink_curriculum():
-    object_0 = object_variable(
-        "object_0", required_properties=[HOLLOW], banned_properties=[IS_BODY_PART]
-    )
+    object_0 = _standard_object("object_0", required_properties=[HOLLOW])
     liquid_0 = object_variable("liquid_0", required_properties=[LIQUID])
     person_0 = object_variable("person_0", PERSON)
 
@@ -778,13 +785,8 @@ def _make_drink_curriculum():
 
 
 def _make_eat_curriculum():
-    object_to_eat = object_variable(
-        "object_0",
-        INANIMATE_OBJECT,
-        required_properties=[EDIBLE],
-        banned_properties=[LIQUID],
-    )
-    eater = object_variable("eater_0", THING, required_properties=[ANIMATE])
+    object_to_eat = _standard_object("object_0", required_properties=[EDIBLE])
+    eater = _standard_object("eater_0", THING, required_properties=[ANIMATE])
 
     # "Mom eats a cookie"
     eat_object = Phase1SituationTemplate(
@@ -907,11 +909,7 @@ 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],
-    )
+    object_taken = _standard_object("object_taken_0", required_properties=[INANIMATE])
 
     # X puts Y on Z
     take_template = Phase1SituationTemplate(
@@ -946,17 +944,9 @@ def _make_move_curriculum():
     )
 
     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],
+    movee_0 = _standard_object("movee_0", THING, required_properties=[INANIMATE])
+    move_goal_reference = _standard_object(
+        "move-goal-reference", THING, required_properties=[INANIMATE]
     )
 
     # since we lack other prepositions at the moment,
@@ -1012,12 +1002,7 @@ def _make_spin_curriculum():
     self_turner = object_variable("self-spinner_0", THING, required_properties=[ANIMATE])
 
     other_spinner = object_variable("spinner_0", THING, required_properties=[ANIMATE])
-    spinee = object_variable(
-        "spinee_0",
-        THING,
-        required_properties=[INANIMATE],
-        banned_properties=[IS_BODY_PART],
-    )
+    spinee = _standard_object("spinee_0", THING, required_properties=[INANIMATE])
 
     bare_spin_template = Phase1SituationTemplate(
         "bare-spin",
@@ -1125,13 +1110,11 @@ def _make_go_curriculum():
 
 def _make_push_curriculum():
     pusher = object_variable("pusher", THING, required_properties=[ANIMATE])
-    pushee = object_variable("pushee", INANIMATE_OBJECT, banned_properties=[IS_BODY_PART])
-    push_surface = object_variable(
+    pushee = _standard_object("pushee", INANIMATE_OBJECT)
+    push_surface = _standard_object(
         "push_surface", THING, required_properties=[CAN_HAVE_THINGS_RESTING_ON_THEM]
     )
-    push_goal_reference = object_variable(
-        "push_goal", INANIMATE_OBJECT, banned_properties=[IS_BODY_PART]
-    )
+    push_goal_reference = _standard_object("push_goal", INANIMATE_OBJECT)
 
     # push with implicit goal
     aux_bindings = [
@@ -1207,6 +1190,76 @@ def _make_push_curriculum():
     )
 
 
+def _make_throw_curriculum():
+    thrower = object_variable("thrower_0", required_properties=[ANIMATE])
+    object_thrown = object_variable(
+        "object_0",
+        required_properties=[INANIMATE],
+        banned_properties=[IS_BODY_PART, LIQUID],
+    )
+    implicit_goal_reference = object_variable("implicit_throw_goal_object", BOX)
+
+    # Dad throws a cookie on the ground
+    throw_on_ground_template = Phase1SituationTemplate(
+        "throw-on-ground",
+        salient_object_variables=[thrower, object_thrown, _GROUND_OBJECT],
+        actions=[
+            Action(
+                THROW,
+                argument_roles_to_fillers=[
+                    (AGENT, thrower),
+                    (THEME, object_thrown),
+                    (
+                        GOAL,
+                        Region(
+                            _GROUND_OBJECT,
+                            distance=EXTERIOR_BUT_IN_CONTACT,
+                            direction=GRAVITATIONAL_UP,
+                        ),
+                    ),
+                ],
+            )
+        ],
+        constraining_relations=[Relation(BIGGER_THAN, thrower, object_thrown)],
+    )
+
+    # A baby throws a truck
+    throw_template = Phase1SituationTemplate(
+        "throw",
+        salient_object_variables=[thrower, object_thrown],
+        actions=[
+            Action(
+                THROW,
+                argument_roles_to_fillers=[(AGENT, thrower), (THEME, object_thrown)],
+                auxiliary_variable_bindings=[
+                    (THROW_GOAL, Region(implicit_goal_reference, distance=PROXIMAL))
+                ],
+            )
+        ],
+        constraining_relations=[Relation(BIGGER_THAN, thrower, object_thrown)],
+    )
+
+    return _phase1_instances(
+        "throwing",
+        chain(
+            *[
+                sampled(
+                    throw_on_ground_template,
+                    max_to_sample=25,
+                    chooser=_CHOOSER,
+                    ontology=GAILA_PHASE_1_ONTOLOGY,
+                ),
+                sampled(
+                    throw_template,
+                    max_to_sample=25,
+                    chooser=_CHOOSER,
+                    ontology=GAILA_PHASE_1_ONTOLOGY,
+                ),
+            ]
+        ),
+    )
+
+
 GAILA_PHASE_1_CURRICULUM = [
     EACH_OBJECT_BY_ITSELF_SUB_CURRICULUM,
     OBJECTS_WITH_COLORS_SUB_CURRICULUM,
@@ -1230,6 +1283,7 @@ GAILA_PHASE_1_CURRICULUM = [
     _make_spin_curriculum(),
     _make_go_curriculum(),
     _make_push_curriculum(),
+    _make_throw_curriculum(),
 ]
 """
 One particular instantiation of the curriculum for GAILA Phase 1.
diff --git a/adam/curriculum_to_html.py b/adam/curriculum_to_html.py
index 2da58048..933c339e 100644
--- a/adam/curriculum_to_html.py
+++ b/adam/curriculum_to_html.py
@@ -20,7 +20,7 @@ from adam.experiment import InstanceGroup
 from adam.language.dependency import LinearizedDependencyTree
 from adam.ontology import IN_REGION
 from adam.ontology.during import DuringAction
-from adam.ontology.phase1_ontology import PART_OF
+from adam.ontology.phase1_ontology import PART_OF, SMALLER_THAN, BIGGER_THAN
 from adam.ontology.phase1_spatial_relations import Region, SpatialPath
 from adam.perception import ObjectPerception, PerceptualRepresentation
 from adam.perception.developmental_primitive_perception import (
@@ -29,6 +29,7 @@ from adam.perception.developmental_primitive_perception import (
     HasColor,
     PropertyPerception,
 )
+from adam.relation import Relation
 from adam.situation import SituationObject
 from adam.situation.high_level_semantics_situation import HighLevelSemanticsSituation
 
@@ -495,9 +496,37 @@ class CurriculumToHtmlDumper:
                     (relation_prefix, relation_suffix) = compute_arrow(
                         relation, static_relations, first_frame_relations
                     )
-                    output_text.append(
-                        f"\t\t\t\t\t\t<li>{relation_prefix}{relation}{relation_suffix}</li>"
-                    )
+                    # if matching smallerThan/biggerThan relations exist, give as single relation
+                    opposite_relations = {
+                        SMALLER_THAN: BIGGER_THAN,
+                        BIGGER_THAN: SMALLER_THAN,
+                    }
+                    single_size_relation = None
+                    if relation.relation_type in opposite_relations:
+                        if (
+                            Relation(
+                                opposite_relations[relation.relation_type],
+                                relation.second_slot,
+                                relation.first_slot,
+                            )
+                            in all_relations
+                        ):
+                            if relation.relation_type == SMALLER_THAN:
+                                single_size_relation = (
+                                    f"{relation.second_slot} > {relation.first_slot}"
+                                )
+                            else:
+                                single_size_relation = (
+                                    f"{relation.first_slot} > {relation.second_slot}"
+                                )
+                    if single_size_relation:
+                        size_output = f"\t\t\t\t\t\t<li>{relation_prefix}{single_size_relation}{relation_suffix}</li>"
+                        if size_output not in output_text:
+                            output_text.append(size_output)
+                    else:
+                        output_text.append(
+                            f"\t\t\t\t\t\t<li>{relation_prefix}{relation}{relation_suffix}</li>"
+                        )
             output_text.append("\t\t\t\t\t</ul>")
 
         if perception.during:
diff --git a/adam/ontology/phase1_ontology.py b/adam/ontology/phase1_ontology.py
index bb5a6a55..406e6949 100644
--- a/adam/ontology/phase1_ontology.py
+++ b/adam/ontology/phase1_ontology.py
@@ -1775,27 +1775,14 @@ _FALL_ACTION_DESCRIPTION = ActionDescription(
 
 _THROW_AGENT = SituationObject(THING, properties=[ANIMATE])
 _THROW_THEME = SituationObject(INANIMATE_OBJECT)
-_THROW_GOAL = SituationObject(THING)
+THROW_GOAL = SituationObject(THING)
 _THROW_MANIPULATOR = SituationObject(THING, properties=[CAN_MANIPULATE_OBJECTS])
 _THROW_GROUND = SituationObject(GROUND)
 
-_THROW_ACTION_DESCRIPTION = ActionDescription(
-    frame=ActionDescriptionFrame(
-        {AGENT: _THROW_AGENT, THEME: _THROW_THEME, GOAL: _THROW_GOAL}
-    ),
-    enduring_conditions=[
-        bigger_than(_THROW_AGENT, _THROW_THEME),
-        partOf(_THROW_MANIPULATOR, _THROW_AGENT),
-    ],
-    preconditions=[
-        has(_THROW_AGENT, _THROW_THEME),
-        contacts(_THROW_MANIPULATOR, _THROW_THEME),
-    ],
-    postconditions=[
-        Relation(IN_REGION, _THROW_THEME, _THROW_GOAL),
-        negate(contacts(_THROW_MANIPULATOR, _THROW_THEME)),
-    ],
-    during=DuringAction(
+
+def _make_throw_descriptions() -> Iterable[Tuple[OntologyNode, ActionDescription]]:
+    during: DuringAction[SituationObject] = DuringAction(
+        objects_to_paths=[(_THROW_THEME, SpatialPath(TO, THROW_GOAL))],
         # must be above the ground at some point during the action
         at_some_point=[
             Relation(
@@ -1809,14 +1796,46 @@ _THROW_ACTION_DESCRIPTION = ActionDescription(
                     ),
                 ),
             )
-        ]
-    ),
-    asserted_properties=[
+        ],
+    )
+    enduring = [
+        partOf(_THROW_MANIPULATOR, _THROW_AGENT),
+        bigger_than(_THROW_AGENT, _THROW_THEME),
+    ]
+    preconditions = [
+        has(_THROW_AGENT, _THROW_THEME),
+        contacts(_THROW_MANIPULATOR, _THROW_THEME),
+    ]
+    postconditions = [
+        inside(_THROW_THEME, THROW_GOAL),
+        negate(contacts(_THROW_MANIPULATOR, _THROW_THEME)),
+    ]
+    asserted_properties = [
         (_THROW_AGENT, VOLITIONALLY_INVOLVED),
         (_THROW_AGENT, CAUSES_CHANGE),
         (_THROW_THEME, UNDERGOES_CHANGE),
-    ],
-)
+    ]
+    # explicit goal
+    yield THROW, ActionDescription(
+        frame=ActionDescriptionFrame(
+            {AGENT: _THROW_AGENT, THEME: _THROW_THEME, GOAL: THROW_GOAL}
+        ),
+        during=during,
+        enduring_conditions=enduring,
+        preconditions=preconditions,
+        postconditions=postconditions,
+        asserted_properties=asserted_properties,
+    )
+    # implicit goal
+    yield THROW, ActionDescription(
+        frame=ActionDescriptionFrame({AGENT: _THROW_AGENT, THEME: _THROW_THEME}),
+        during=during,
+        enduring_conditions=enduring,
+        preconditions=preconditions,
+        postconditions=postconditions,
+        asserted_properties=asserted_properties,
+    )
+
 
 _MOVE_AGENT = SituationObject(THING, properties=[ANIMATE])
 _MOVE_THEME = SituationObject(THING)
@@ -1976,7 +1995,6 @@ _ACTIONS_TO_DESCRIPTIONS = [
     (TAKE, _TAKE_ACTION_DESCRIPTION),
     (EAT, _EAT_ACTION_DESCRIPTION),
     (FALL, _FALL_ACTION_DESCRIPTION),
-    (THROW, _THROW_ACTION_DESCRIPTION),
     (FLY, _FLY_ACTION_DESCRIPTION),
 ]
 
@@ -1988,6 +2006,7 @@ _ACTIONS_TO_DESCRIPTIONS.extend(_make_move_descriptions())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_spin_descriptions())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_go_description())
 _ACTIONS_TO_DESCRIPTIONS.extend(_make_push_descriptions())
+_ACTIONS_TO_DESCRIPTIONS.extend(_make_throw_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 dfa5e351..2e2df5c4 100644
--- a/tests/curriculum/phase1_curriculum_test.py
+++ b/tests/curriculum/phase1_curriculum_test.py
@@ -15,6 +15,7 @@ from adam.curriculum.phase1_curriculum import (
     _make_spin_curriculum,
     _make_go_curriculum,
     _make_push_curriculum,
+    _make_throw_curriculum,
 )
 
 
@@ -85,3 +86,7 @@ def test_go():
 
 def test_push():
     _test_curriculum(_make_push_curriculum())
+
+
+def test_throw():
+    _test_curriculum(_make_throw_curriculum())