Example #1
0
class BaseMutexTest(unittest.TestCase):
    def setUp(self):
        self.cake_problem = have_cake()
        self.cake_pg = PlanningGraph(self.cake_problem,
                                     self.cake_problem.initial,
                                     serialize=False).fill()

        self.eat_action, self.bake_action = [
            a for a in self.cake_pg._actionNodes if not a.no_op
        ]
        no_ops = [a for a in self.cake_pg._actionNodes if a.no_op]
        self.null_action = make_node(
            Action(expr('Null()'), [set(), set()], [set(), set()]))

        # some independent nodes for testing mutexes
        at_here = expr('At(here)')
        at_there = expr('At(there)')
        self.pos_literals = [at_here, at_there]
        self.neg_literals = [~x for x in self.pos_literals]
        self.literal_layer = LiteralLayer(
            self.pos_literals + self.neg_literals, ActionLayer())
        self.literal_layer.update_mutexes()

        # independent actions for testing mutex
        self.actions = [
            make_node(
                Action(expr('Go(here)'), [set(), set()],
                       [set([at_here]), set()])),
            make_node(
                Action(expr('Go(there)'), [set(), set()],
                       [set([at_there]), set()]))
        ]
        self.no_ops = [
            make_node(x)
            for x in chain(*(makeNoOp(l) for l in self.pos_literals))
        ]
        self.action_layer = ActionLayer(self.no_ops + self.actions,
                                        self.literal_layer)
        self.action_layer.update_mutexes()
        for action in self.no_ops + self.actions:
            self.action_layer.add_inbound_edges(action, action.preconditions)
            self.action_layer.add_outbound_edges(action, action.effects)
Example #2
0
class TestPlanningGraphMutex(unittest.TestCase):
    def setUp(self):
        self.cake_problem = have_cake()
        self.cake_pg = PlanningGraph(self.cake_problem,
                                     self.cake_problem.initial,
                                     serialize=False).fill()

        eat_action, bake_action = [
            a for a in self.cake_pg._actionNodes if not a.no_op
        ]
        no_ops = [a for a in self.cake_pg._actionNodes if a.no_op]

        # bake has the effect Have(Cake) which is the logical negation of the effect
        # ~Have(cake) from the persistence action ~NoOp::Have(cake)
        self.inconsistent_effects_actions = [bake_action, no_ops[3]]

        # the persistence action ~NoOp::Have(cake) has the effect ~Have(cake), which is
        # the logical negation of Have(cake) -- the precondition for the Eat(cake) action
        self.interference_actions = [eat_action, no_ops[3]]

        # eat has precondition Have(cake) and bake has precondition ~Have(cake)
        # which are logical inverses, so eat & bake should be mutex at every
        # level of the planning graph where both actions appear
        self.competing_needs_actions = [eat_action, bake_action]

        self.ac_problem = air_cargo_p1()
        self.ac_pg_serial = PlanningGraph(self.ac_problem,
                                          self.ac_problem.initial).fill()
        # In(C1, P2) and In(C2, P1) have inconsistent support when they first appear in
        # the air cargo problem,
        self.inconsistent_support_literals = [
            expr("In(C1, P2)"), expr("In(C2, P1)")
        ]

        # some independent nodes for testing mutexes
        at_here = expr('At(here)')
        at_there = expr('At(there)')
        self.pos_literals = [at_here, at_there]
        self.neg_literals = [~x for x in self.pos_literals]
        self.literal_layer = LiteralLayer(
            self.pos_literals + self.neg_literals, ActionLayer())
        self.literal_layer.update_mutexes()

        # independent actions for testing mutex
        self.actions = [
            make_node(
                Action(expr('Go(here)'), [set(), set()],
                       [set([at_here]), set()])),
            make_node(
                Action(expr('Go(there)'), [set(), set()],
                       [set([at_there]), set()]))
        ]
        self.no_ops = [
            make_node(x)
            for x in chain(*(makeNoOp(l) for l in self.pos_literals))
        ]
        self.action_layer = ActionLayer(self.no_ops + self.actions,
                                        self.literal_layer)
        self.action_layer.update_mutexes()
        for action in self.no_ops + self.actions:
            self.action_layer.add_inbound_edges(action, action.preconditions)
            self.action_layer.add_outbound_edges(action, action.effects)

        # competing needs tests -- build two copies of the planning graph: one where
        #  A, B, and C are pairwise mutex, and another where they are not
        A, B, C = expr('A'), expr('B'), expr('C')
        self.fake_competing_needs_actions = [
            make_node(
                Action(expr('FakeAction(A)'), [set([A]), set()],
                       [set([A]), set()])),
            make_node(
                Action(expr('FakeAction(B)'), [set([B]), set()],
                       [set([B]), set()])),
            make_node(
                Action(expr('FakeAction(C)'), [set([C]), set()],
                       [set([C]), set()]))
        ]
        competing_layer = LiteralLayer([A, B, C], ActionLayer())
        for a1, a2 in combinations([A, B, C], 2):
            competing_layer.set_mutex(a1, a2)
        self.competing_action_layer = ActionLayer(competing_layer.parent_layer,
                                                  competing_layer, False, True)
        for action in self.fake_competing_needs_actions:
            self.competing_action_layer.add(action)
            competing_layer |= action.effects
            competing_layer.add_outbound_edges(action, action.preconditions)
            self.competing_action_layer.add_inbound_edges(
                action, action.preconditions)
            self.competing_action_layer.add_outbound_edges(
                action, action.effects)

        not_competing_layer = LiteralLayer([A, B, C], ActionLayer())
        self.not_competing_action_layer = ActionLayer(
            not_competing_layer.parent_layer, not_competing_layer, False, True)
        for action in self.fake_competing_needs_actions:
            self.not_competing_action_layer.add(action)
            not_competing_layer |= action.effects
            not_competing_layer.add_outbound_edges(action,
                                                   action.preconditions)
            self.not_competing_action_layer.add_inbound_edges(
                action, action.preconditions)
            self.not_competing_action_layer.add_outbound_edges(
                action, action.effects)

    def test_inconsistent_effects_mutex(self):
        acts = [self.actions[0], self.no_ops[0]]
        self.assertFalse(
            self.action_layer._inconsistent_effects(*acts),
            "'{!s}' and '{!s}' should NOT be mutually exclusive by inconsistent effects"
            .format(*acts))
        acts = [self.actions[0], self.no_ops[1]]
        self.assertTrue(
            self.action_layer._inconsistent_effects(*acts),
            "'{!s}' and '{!s}' should NOT be mutually exclusive by inconsistent effects"
            .format(*acts))

        # inconsistent effects mutexes are static -- they should appear in the last layer of the planning graph
        for idx, layer in enumerate(self.cake_pg.action_layers):
            if set(self.inconsistent_effects_actions) <= layer:
                self.assertTrue(
                    layer.is_mutex(*self.inconsistent_effects_actions),
                    ("Actions {} and {} were not mutex in layer {} of the planning graph"
                     ).format(self.inconsistent_effects_actions[0],
                              self.inconsistent_effects_actions[1], idx))

    def test_interference_mutex(self):
        acts = [self.actions[0], self.actions[1]]
        self.assertFalse(
            self.action_layer._interference(*acts),
            "'{!s}' and '{!s}' should NOT be mutually exclusive by interference"
            .format(*acts))
        acts = [self.actions[0], self.no_ops[1]]
        self.assertTrue(
            self.action_layer._interference(*acts),
            "'{!s}' and '{!s}' should NOT be mutually exclusive by interference"
            .format(*acts))

        # interference mutexes are static -- they should appear in the last layer of the planning graph
        for idx, layer in enumerate(self.cake_pg.action_layers):
            if set(self.interference_actions) <= layer:
                self.assertTrue(layer.is_mutex(*self.interference_actions), (
                    "Actions {} and {} were not mutex in layer {} of the planning graph"
                ).format(self.interference_actions[0],
                         self.interference_actions[1], idx))

    def test_competing_needs_mutex(self):
        acts = [self.no_ops[0], self.no_ops[2]]
        self.assertFalse(
            self.action_layer._competing_needs(*acts),
            "'{!s}' and '{!s}' should NOT be mutually exclusive by competing needs"
            .format(*acts))
        acts = [self.no_ops[0], self.no_ops[1]]
        self.assertTrue(
            self.action_layer._competing_needs(*acts),
            "'{!s}' and '{!s}' should be mutually exclusive by competing needs"
            .format(*acts))

        for acts in combinations(self.fake_competing_needs_actions, 2):
            self.assertFalse(
                self.not_competing_action_layer._competing_needs(*acts),
                ("'{!s}' and '{!s}' should NOT be mutually exclusive by competing needs unless "
                 +
                 "every pair of actions is mutex in the parent layer").format(
                     *acts))

        for acts in combinations(self.fake_competing_needs_actions, 2):
            self.assertTrue(
                self.competing_action_layer._competing_needs(*acts),
                ("'{!s}' and '{!s}' should be mutually exclusive by competing needs if every "
                 +
                 "pair of actions is mutex in the parent layer").format(*acts))

        # competing needs mutexes are dynamic -- they only appear in some levels of the planning graph
        for idx, layer in enumerate(self.cake_pg.action_layers):
            if set(self.competing_needs_actions) <= layer:
                self.assertTrue(layer.is_mutex(
                    *self.competing_needs_actions
                ), ("Actions {} and {} were not mutex in layer {} of the planning graph"
                    ).format(self.competing_needs_actions[0],
                             self.competing_needs_actions[1], idx))

    def test_negation_mutex(self):
        lits = [self.pos_literals[0], self.neg_literals[0]]
        self.assertTrue(
            self.literal_layer._negation(*lits),
            "The literals '{}' and '{}' should be mutually exclusive by negation"
            .format(*lits))
        lits = [self.pos_literals[0], self.neg_literals[1]]
        self.assertFalse(
            self.literal_layer._negation(*lits),
            "The literals '{}' and '{}' should NOT be mutually exclusive by negation"
            .format(*lits))

        # Negation mutexes are static, so they should appear in every layer of the planning graph
        self.assertTrue(
            all(self.cake_pg.literal_layers[-1].is_mutex(l, ~l)
                for l in self.cake_problem.state_map),
            "One or more literal was not marked as mutex with its negation in the last layer of the planning graph"
        )

    def test_inconsistent_support_mutex(self):
        # inconsistent support mutexes are dynamic -- they should not remain mutex at the last layer
        self.assertTrue(
            self.ac_pg_serial.literal_layers[2]._inconsistent_support(
                *self.inconsistent_support_literals),
            "The literals '{}' and '{}' should be mutually exclusive by inconsistent support in the second layer"
            .format(*self.inconsistent_support_literals))
        self.assertFalse(
            self.ac_pg_serial.literal_layers[-1]._inconsistent_support(
                *self.inconsistent_support_literals),
            "The literals '{}' and '{}' should NOT be mutually exclusive by inconsistent support in the last layer"
            .format(*self.inconsistent_support_literals))