From 011b6a3ce4e2d8ebd260df96b19c0d054f3bcf73 Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Fri, 14 Aug 2020 07:03:55 +0200 Subject: [PATCH 1/8] add method that generates random recipes inside Recipe class --- src/overcooked_ai_py/mdp/overcooked_mdp.py | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/overcooked_ai_py/mdp/overcooked_mdp.py b/src/overcooked_ai_py/mdp/overcooked_mdp.py index 86491fbf..5b892b0a 100644 --- a/src/overcooked_ai_py/mdp/overcooked_mdp.py +++ b/src/overcooked_ai_py/mdp/overcooked_mdp.py @@ -232,6 +232,34 @@ def configure(cls, conf): if 'onion_value' in conf: cls._onion_value = conf['onion_value'] + + @classmethod + def generate_random_recipes(cls, n=1, min_size=2, max_size=3, ingredients=None, recipes=None, unique=True): + """ + n (int): how many recipes generate + min_size (int): min generated recipe size + max_size (int): max generated recipe size + ingredients (list(str)): list of ingredients used for generating recipes (default is cls.ALL_INGREDIENTS) + recipes (list(Recipe)): list of recipes to choose from (default is cls.ALL_RECIPES) + unique (bool): if all recipes are unique (without repeats) + """ + if recipes is None: recipes = cls.ALL_RECIPES + + ingredients = set(ingredients or cls.ALL_INGREDIENTS) + choice_replace = not(unique) + + assert 1 <= min_size <= max_size <= cls.MAX_NUM_INGREDIENTS + assert all(ingredient in cls.ALL_INGREDIENTS for ingredient in ingredients) + + def valid_size(r): + return min_size <= len(r.ingredients) <= max_size + + def valid_ingredients(r): + return all(i in ingredients for i in r.ingredients) + + relevant_recipes = [r for r in recipes if valid_size(r) and valid_ingredients(r)] + assert choice_replace or (n <= len(relevant_recipes)) + return np.random.choice(relevant_recipes, n, replace=choice_replace) @classmethod def from_dict(cls, obj_dict): From 75b33254658195571aa474f1072ed4296ebbb82e Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Fri, 14 Aug 2020 07:05:24 +0200 Subject: [PATCH 2/8] add random recipes generation while generating layout --- src/overcooked_ai_py/mdp/layout_generator.py | 52 ++++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index 48560340..efe57d9e 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -1,8 +1,8 @@ import numpy as np +import copy from overcooked_ai_py.utils import rnd_int_uniform, rnd_uniform from overcooked_ai_py.mdp.actions import Action, Direction -from overcooked_ai_py.mdp.overcooked_mdp import OvercookedGridworld - +from overcooked_ai_py.mdp.overcooked_mdp import OvercookedGridworld, Recipe EMPTY = ' ' COUNTER = 'X' @@ -15,6 +15,7 @@ CODE_TO_TYPE = { 0: EMPTY, 1: COUNTER, 2: ONION_DISPENSER, 3: TOMATO_DISPENSER, 4: POT, 5: DISH_DISPENSER, 6: SERVING_LOC} TYPE_TO_CODE = { v:k for k, v in CODE_TO_TYPE.items() } +DEFAULT_FEATURE_TYPES = [POT, ONION_DISPENSER, DISH_DISPENSER, SERVING_LOC] # NOTE: TOMATO_DISPENSER is disabled by default class LayoutGenerator(object): # NOTE: This class hasn't been tested extensively. @@ -34,6 +35,7 @@ def mdp_gen_fn_from_dict( size_bounds=((4, 7), (4, 7)), prop_empty=(0.6, 0.8), prop_feats=(0.1, 0.2), + feature_types=None, display=False ): """ @@ -46,12 +48,12 @@ def mdp_gen_fn_from_dict( size_bounds: (min_layout_size, max_layout_size) prop_empty: (min, max) proportion of empty space in generated layout prop_feats: (min, max) proportion of counters with features on them + feature_types: list of feature types, by default it is set to DEFAULT_FEATURE_TYPES """ - if "layout_name" in mdp_params.keys() and mdp_params["layout_name"] is not None: + if mdp_params.get("layout_name") is not None: mdp = OvercookedGridworld.from_layout_name(**mdp_params) mdp_generator_fn = lambda: mdp - elif mdp_choices is not None: assert type(mdp_choices) is list @@ -76,11 +78,39 @@ def mdp_generator_fn(): inner_shape=[rnd_int_uniform(*dim) for dim in size_bounds], prop_empty=rnd_uniform(*prop_empty), prop_features=rnd_uniform(*prop_feats), + feature_types=feature_types, display=display ) return mdp_generator_fn + @staticmethod + def add_generated_mdp_params_orders(mdp_params): + """ + adds generated parameters (i.e. generated orders) to mdp_params + """ + mdp_params = copy.deepcopy(mdp_params) + if mdp_params.get("generate_all_orders"): + all_orders_kwargs = copy.deepcopy(mdp_params["generate_all_orders"]) + + if all_orders_kwargs.get("recipes"): + all_orders_kwargs["recipes"] = [r if isinstance(r, Recipe) else Recipe.from_dict(r) for r in all_orders_kwargs["recipes"]] + + all_recipes = Recipe.generate_random_recipes(**all_orders_kwargs) + mdp_params["start_all_orders"] = [r.to_dict() for r in all_recipes] + else: + all_recipes = Recipe.ALL_RECIPES + + if mdp_params.get("generate_bonus_orders"): + bonus_orders_kwargs = copy.deepcopy(mdp_params["generate_bonus_orders"]) + + if not bonus_orders_kwargs.get("recipes"): + bonus_orders_kwargs["recipes"] = all_recipes + + bonus_recipes = Recipe.generate_random_recipes(**bonus_orders_kwargs) + mdp_params["start_bonus_orders"] = [r.to_dict() for r in bonus_recipes] + return mdp_params + def padded_mdp(self, mdp, display=False): """Returns a padded MDP from an MDP""" grid = Grid.from_mdp(mdp) @@ -88,17 +118,19 @@ def padded_mdp(self, mdp, display=False): start_positions = self.get_random_starting_positions(padded_grid) mdp_grid = self.padded_grid_to_layout_grid(padded_grid, start_positions, display=display) - return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=self.mdp_params) + mdp_params = LayoutGenerator.add_generated_mdp_params_orders(self.mdp_params) + return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=mdp_params) - def make_disjoint_sets_layout(self, inner_shape, prop_empty, prop_features, display=True): + def make_disjoint_sets_layout(self, inner_shape, prop_empty, prop_features, display=True, feature_types=None): grid = Grid(inner_shape) self.dig_space_with_disjoint_sets(grid, prop_empty) - self.add_features(grid, prop_features) + self.add_features(grid, prop_features, feature_types) padded_grid = self.embed_grid(grid) start_positions = self.get_random_starting_positions(padded_grid) mdp_grid = self.padded_grid_to_layout_grid(padded_grid, start_positions, display=display) - return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=self.mdp_params) + mdp_params = LayoutGenerator.add_generated_mdp_params_orders(self.mdp_params) + return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=mdp_params) def padded_grid_to_layout_grid(self, padded_grid, start_positions, display=False): if display: @@ -166,11 +198,11 @@ def dig_space_with_fringe_expansion(self, grid, prop_empty=0.1): if grid.is_valid_dig_location(location): fringe.add(location) - def add_features(self, grid, prop_features=0): + def add_features(self, grid, prop_features=0, feature_types=None): """ Places one round of basic features and then adds random features until prop_features of valid locations are filled""" - feature_types = [POT, ONION_DISPENSER, DISH_DISPENSER, SERVING_LOC] # NOTE: currently disabled TOMATO_DISPENSER + if not feature_types: feature_types = DEFAULT_FEATURE_TYPES valid_locations = grid.valid_feature_locations() np.random.shuffle(valid_locations) From 7d02ad540770ffdbd409f1a64f49636b2163c3e5 Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Fri, 14 Aug 2020 07:16:28 +0200 Subject: [PATCH 3/8] add tests for generating recipes --- testing/overcooked_test.py | 79 +++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/testing/overcooked_test.py b/testing/overcooked_test.py index 743c4e30..d5194539 100644 --- a/testing/overcooked_test.py +++ b/testing/overcooked_test.py @@ -1,17 +1,17 @@ import unittest, os +import json import numpy as np from math import factorial from overcooked_ai_py.mdp.actions import Action, Direction from overcooked_ai_py.mdp.overcooked_mdp import PlayerState, OvercookedGridworld, OvercookedState, ObjectState, SoupState, Recipe from overcooked_ai_py.mdp.overcooked_env import OvercookedEnv, DEFAULT_ENV_PARAMS -from overcooked_ai_py.mdp.layout_generator import LayoutGenerator +from overcooked_ai_py.mdp.layout_generator import LayoutGenerator, ONION_DISPENSER, TOMATO_DISPENSER, POT, DISH_DISPENSER, SERVING_LOC from overcooked_ai_py.agents.agent import AgentGroup, AgentPair, GreedyHumanModel, FixedPlanAgent, RandomAgent from overcooked_ai_py.agents.benchmarking import AgentEvaluator from overcooked_ai_py.planning.planners import MediumLevelPlanner, NO_COUNTERS_PARAMS, MotionPlanner from overcooked_ai_py.utils import save_pickle, load_pickle, iterate_over_json_files_in_dir, load_from_json, save_as_json from utils import TESTING_DATA_DIR, generate_serialized_trajectory - START_ORDER_LIST = ["any"] n, s = Direction.NORTH, Direction.SOUTH e, w = Direction.EAST, Direction.WEST @@ -80,6 +80,24 @@ def test_invalid_input(self): self.assertRaises(ValueError, Recipe, []) self.assertRaises(ValueError, Recipe, "invalid argument") + def test_recipes_generation(self): + self.assertRaises(AssertionError, Recipe.generate_random_recipes, max_size=Recipe.MAX_NUM_INGREDIENTS+1) + self.assertRaises(AssertionError, Recipe.generate_random_recipes, min_size=0) + self.assertRaises(AssertionError, Recipe.generate_random_recipes, min_size=3, max_size=2) + self.assertRaises(AssertionError, Recipe.generate_random_recipes, ingredients=["onion", "tomato", "fake_ingredient"]) + self.assertRaises(AssertionError, Recipe.generate_random_recipes, n=99999) + self.assertEqual(len(Recipe.generate_random_recipes(n=3)), 3) + self.assertEqual(len(Recipe.generate_random_recipes(n=99, unique=False)), 99) + + two_sized_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "tomato"]), Recipe(["tomato", "tomato"])] + for _ in range(100): + self.assertCountEqual(two_sized_recipes, Recipe.generate_random_recipes(n=3, min_size=2, max_size=2, ingredients=["onion", "tomato"])) + + only_onions_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "onion", "onion"])] + for _ in range(100): + self.assertCountEqual(only_onions_recipes, Recipe.generate_random_recipes(n=2, min_size=2, max_size=3, ingredients=["onion"])) + + self.assertCountEqual(only_onions_recipes, set([Recipe.generate_random_recipes(n=1, recipes=only_onions_recipes)[0] for _ in range(100)])) # false positives rate for this test is 1/10^99 def _expected_num_recipes(self, num_ingredients, max_len): return comb(num_ingredients + max_len, num_ingredients) - 1 @@ -840,6 +858,63 @@ def test_random_layout(self): all_same_layout = all([np.array_equal(env.mdp.terrain_mtx, terrain) for terrain in layouts_seen]) self.assertFalse(all_same_layout) + def test_random_layout_feature_types(self): + mandatory_features = {POT, DISH_DISPENSER, SERVING_LOC} + optional_features = {ONION_DISPENSER, TOMATO_DISPENSER} + optional_features_combinations = [{ONION_DISPENSER, TOMATO_DISPENSER}, {ONION_DISPENSER}, {TOMATO_DISPENSER}] + + for optional_features_combo in optional_features_combinations: + left_out_optional_features = optional_features - optional_features_combo + used_features = list(optional_features_combo | mandatory_features) + mdp_gen_params = {"prop_feats": (1, 1), + "feature_types": used_features} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) + for _ in range(10): + env.reset() + curr_terrain = env.mdp.terrain_mtx + terrain_features = set.union(*(set(line) for line in curr_terrain)) + self.assertTrue(all(elem in terrain_features for elem in used_features)) # all used_features are actually used + if left_out_optional_features: + self.assertFalse(any(elem in terrain_features for elem in left_out_optional_features)) # all left_out optional_features are not used + + def test_random_layout_generated_recipes(self): + only_onions_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "onion", "onion"])] + only_onions_dict_recipes = [r.to_dict() for r in only_onions_recipes] + + # checking if recipes are generated from mdp_params + mdp_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}} + mdp_gen_params = {"mdp_params": mdp_params} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) + for _ in range(10): + env.reset() + self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) + self.assertTrue(len(env.mdp.start_bonus_orders) == 0) + + # checking if bonus_orders is subset of all_orders even if not specified + mdp_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, + "generate_bonus_orders": {"n":1, "min_size":2, "max_size":3}} + mdp_gen_params = {"mdp_params": mdp_params} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) + for _ in range(10): + env.reset() + self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) + self.assertTrue(len(env.mdp.start_bonus_orders) == 1) + self.assertTrue(env.mdp.start_bonus_orders[0] in only_onions_dict_recipes) + + # checking if after reset there are new recipes generated + mdp_params = {"generate_all_orders": {"n":3, "min_size":2, "max_size":3}} + mdp_gen_params = {"mdp_params": mdp_params} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) + generated_recipes_strings = set() + for _ in range(20): + env.reset() + generated_recipes_strings |= {json.dumps(o, sort_keys=True) for o in env.mdp.start_all_orders} + self.assertTrue(len(generated_recipes_strings) > 3) + class TestGymEnvironment(unittest.TestCase): From a9ebbe7ee55c79a09ef37abb88fadbc8aedd92bd Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Tue, 18 Aug 2020 11:24:48 +0200 Subject: [PATCH 4/8] force mdp params[generate_all_orders][recipes] be json serializable --- src/overcooked_ai_py/mdp/layout_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index efe57d9e..8ec0efd9 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -94,7 +94,7 @@ def add_generated_mdp_params_orders(mdp_params): all_orders_kwargs = copy.deepcopy(mdp_params["generate_all_orders"]) if all_orders_kwargs.get("recipes"): - all_orders_kwargs["recipes"] = [r if isinstance(r, Recipe) else Recipe.from_dict(r) for r in all_orders_kwargs["recipes"]] + all_orders_kwargs["recipes"] = [Recipe.from_dict(r) for r in all_orders_kwargs["recipes"]] all_recipes = Recipe.generate_random_recipes(**all_orders_kwargs) mdp_params["start_all_orders"] = [r.to_dict() for r in all_recipes] From 3a81f23da0f05569da6cadc14e62f0b6a9495bf7 Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Tue, 25 Aug 2020 23:23:42 +0200 Subject: [PATCH 5/8] DEFAULT_FEATURE_TYPES is now tuple immutable data type that can be used as default parameter without risk --- src/overcooked_ai_py/mdp/layout_generator.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index 8ec0efd9..cf0ba755 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -15,7 +15,7 @@ CODE_TO_TYPE = { 0: EMPTY, 1: COUNTER, 2: ONION_DISPENSER, 3: TOMATO_DISPENSER, 4: POT, 5: DISH_DISPENSER, 6: SERVING_LOC} TYPE_TO_CODE = { v:k for k, v in CODE_TO_TYPE.items() } -DEFAULT_FEATURE_TYPES = [POT, ONION_DISPENSER, DISH_DISPENSER, SERVING_LOC] # NOTE: TOMATO_DISPENSER is disabled by default +DEFAULT_FEATURE_TYPES = (POT, ONION_DISPENSER, DISH_DISPENSER, SERVING_LOC) # NOTE: TOMATO_DISPENSER is disabled by default class LayoutGenerator(object): # NOTE: This class hasn't been tested extensively. @@ -35,7 +35,7 @@ def mdp_gen_fn_from_dict( size_bounds=((4, 7), (4, 7)), prop_empty=(0.6, 0.8), prop_feats=(0.1, 0.2), - feature_types=None, + feature_types=DEFAULT_FEATURE_TYPES, display=False ): """ @@ -48,7 +48,7 @@ def mdp_gen_fn_from_dict( size_bounds: (min_layout_size, max_layout_size) prop_empty: (min, max) proportion of empty space in generated layout prop_feats: (min, max) proportion of counters with features on them - feature_types: list of feature types, by default it is set to DEFAULT_FEATURE_TYPES + feature_types: tuple (or list) of feature types """ if mdp_params.get("layout_name") is not None: @@ -121,7 +121,7 @@ def padded_mdp(self, mdp, display=False): mdp_params = LayoutGenerator.add_generated_mdp_params_orders(self.mdp_params) return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=mdp_params) - def make_disjoint_sets_layout(self, inner_shape, prop_empty, prop_features, display=True, feature_types=None): + def make_disjoint_sets_layout(self, inner_shape, prop_empty, prop_features, display=True, feature_types=DEFAULT_FEATURE_TYPES): grid = Grid(inner_shape) self.dig_space_with_disjoint_sets(grid, prop_empty) self.add_features(grid, prop_features, feature_types) @@ -198,11 +198,10 @@ def dig_space_with_fringe_expansion(self, grid, prop_empty=0.1): if grid.is_valid_dig_location(location): fringe.add(location) - def add_features(self, grid, prop_features=0, feature_types=None): + def add_features(self, grid, prop_features=0, feature_types=DEFAULT_FEATURE_TYPES): """ Places one round of basic features and then adds random features until prop_features of valid locations are filled""" - if not feature_types: feature_types = DEFAULT_FEATURE_TYPES valid_locations = grid.valid_feature_locations() np.random.shuffle(valid_locations) From a68ced6b4837d807c0876af9fc96d8e7b7062da0 Mon Sep 17 00:00:00 2001 From: mesutyang97 Date: Sat, 29 Aug 2020 11:52:31 -0700 Subject: [PATCH 6/8] minor typo update --- src/overcooked_ai_py/mdp/layout_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index 1118bcbe..cdfd020a 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -159,7 +159,7 @@ def generate_padded_mdp(self, outside_information={}): prop_empty=mdp_gen_params["prop_empty"], prop_features=mdp_gen_params["prop_feats"], base_param=recipe_params, - feature_types=mdp_gen_params["feature_types"] + feature_types=mdp_gen_params["feature_types"], display=mdp_gen_params["display"] ) From 2d130e9b00301e1b1b059c11d35ff4e3cdec5566 Mon Sep 17 00:00:00 2001 From: mesutyang97 Date: Sat, 29 Aug 2020 11:55:16 -0700 Subject: [PATCH 7/8] minor update 2 --- src/overcooked_ai_py/mdp/layout_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index cdfd020a..0b109d44 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -3,7 +3,7 @@ import random, copy from overcooked_ai_py.utils import rnd_int_uniform, rnd_uniform from overcooked_ai_py.mdp.actions import Action, Direction -from overcooked_ai_py.mdp.overcooked_mdp import OvercookedGridworld +from overcooked_ai_py.mdp.overcooked_mdp import OvercookedGridworld, Recipe EMPTY = ' ' COUNTER = 'X' From af85350ca6a817151f3d7b1aeb41739b8094aa99 Mon Sep 17 00:00:00 2001 From: bmielnicki Date: Sat, 29 Aug 2020 23:25:27 +0200 Subject: [PATCH 8/8] fix tests and layout_generator after merge --- src/overcooked_ai_py/mdp/layout_generator.py | 58 ++++++++++++-------- testing/overcooked_test.py | 48 +++++++++++----- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/overcooked_ai_py/mdp/layout_generator.py b/src/overcooked_ai_py/mdp/layout_generator.py index 0b109d44..80474486 100644 --- a/src/overcooked_ai_py/mdp/layout_generator.py +++ b/src/overcooked_ai_py/mdp/layout_generator.py @@ -128,47 +128,49 @@ def generate_padded_mdp(self, outside_information={}): Return a PADDED MDP with mdp params specified in self.mdp_params """ mdp_gen_params = self.mdp_params_generator.generate(outside_information) + outer_shape = self.outer_shape if "layout_name" in mdp_gen_params.keys() and mdp_gen_params["layout_name"] is not None: mdp = OvercookedGridworld.from_layout_name(**mdp_gen_params) mdp_generator_fn = lambda: self.padded_mdp(mdp) else: - required_keys = ["inner_shape", "prop_empty", "prop_feats", "display"] + # with generate_all_orders key start_all_orders will be generated inside make_new_layout method + if not mdp_gen_params.get("generate_all_orders"): + required_keys.append("start_all_orders") missing_keys = [k for k in required_keys if k not in mdp_gen_params.keys()] + if len(missing_keys) != 0: + print("missing keys dict", mdp_gen_params) assert len(missing_keys) == 0, "These keys were missing from the mdp_params: {}".format(missing_keys) inner_shape = mdp_gen_params["inner_shape"] assert inner_shape[0] <= outer_shape[0] and inner_shape[1] <= outer_shape[1], \ "inner_shape cannot fit into the outershap" layout_generator = LayoutGenerator(self.mdp_params_generator, outer_shape=self.outer_shape) - if "start_all_orders" in mdp_gen_params: - recipe_params = {"start_all_orders": mdp_gen_params["start_all_orders"]} - if "recipe_values" in mdp_gen_params: - recipe_params["recipe_values"] = mdp_gen_params["recipe_values"] - if "recipe_times" in mdp_gen_params: - recipe_params["recipe_times"] = mdp_gen_params["recipe_times"] - else: - recipe_params = LayoutGenerator.add_generated_mdp_params_orders(self.mdp_params) - if "feature_types" not in mdp_gen_params: mdp_gen_params["feature_types"] = DEFAULT_FEATURE_TYPES - mdp_generator_fn = lambda: layout_generator.make_disjoint_sets_layout( - inner_shape=mdp_gen_params["inner_shape"], - prop_empty=mdp_gen_params["prop_empty"], - prop_features=mdp_gen_params["prop_feats"], - base_param=recipe_params, - feature_types=mdp_gen_params["feature_types"], - display=mdp_gen_params["display"] - ) - + mdp_generator_fn = lambda: layout_generator.make_new_layout(mdp_gen_params) return mdp_generator_fn() - + + @staticmethod + def create_base_params(mdp_gen_params): + assert mdp_gen_params.get("start_all_orders") or mdp_gen_params.get("generate_all_orders") + mdp_gen_params = LayoutGenerator.add_generated_mdp_params_orders(mdp_gen_params) + recipe_params = {"start_all_orders": mdp_gen_params["start_all_orders"]} + if mdp_gen_params.get("start_bonus_orders"): + recipe_params["start_bonus_orders"] = mdp_gen_params["start_bonus_orders"] + if "recipe_values" in mdp_gen_params: + recipe_params["recipe_values"] = mdp_gen_params["recipe_values"] + if "recipe_times" in mdp_gen_params: + recipe_params["recipe_times"] = mdp_gen_params["recipe_times"] + return recipe_params + @staticmethod def add_generated_mdp_params_orders(mdp_params): """ - adds generated parameters (i.e. generated orders) to mdp_params + adds generated parameters (i.e. generated orders) to mdp_params, + returns onchanged copy of mdp_params when there is no "generate_all_orders" and "generate_bonus_orders" keys inside mdp_params """ mdp_params = copy.deepcopy(mdp_params) if mdp_params.get("generate_all_orders"): @@ -199,10 +201,18 @@ def padded_mdp(self, mdp, display=False): start_positions = self.get_random_starting_positions(padded_grid) mdp_grid = self.padded_grid_to_layout_grid(padded_grid, start_positions, display=display) + return OvercookedGridworld.from_grid(mdp_grid) + + def make_new_layout(self, mdp_gen_params): + return self.make_disjoint_sets_layout( + inner_shape=mdp_gen_params["inner_shape"], + prop_empty=mdp_gen_params["prop_empty"], + prop_features=mdp_gen_params["prop_feats"], + base_param=LayoutGenerator.create_base_params(mdp_gen_params), + feature_types=mdp_gen_params["feature_types"], + display=mdp_gen_params["display"] + ) - mdp_params = LayoutGenerator.add_generated_mdp_params_orders(self.mdp_params) - return OvercookedGridworld.from_grid(mdp_grid, base_layout_params=mdp_params) - def make_disjoint_sets_layout(self, inner_shape, prop_empty, prop_features, base_param, feature_types=DEFAULT_FEATURE_TYPES, display=True): grid = Grid(inner_shape) self.dig_space_with_disjoint_sets(grid, prop_empty) diff --git a/testing/overcooked_test.py b/testing/overcooked_test.py index 9713016a..542bc4d4 100644 --- a/testing/overcooked_test.py +++ b/testing/overcooked_test.py @@ -899,9 +899,15 @@ def test_random_layout_feature_types(self): for optional_features_combo in optional_features_combinations: left_out_optional_features = optional_features - optional_features_combo used_features = list(optional_features_combo | mandatory_features) - mdp_gen_params = {"prop_feats": (1, 1), - "feature_types": used_features} - mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + mdp_gen_params = {"prop_feats": 0.9, + "feature_types": used_features, + "prop_empty": 0.1, + "inner_shape": (6, 5), + "display": False, + "start_all_orders" : [ + { "ingredients" : ["onion", "onion", "onion"]} + ]} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6, 5)) env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) for _ in range(10): env.reset() @@ -916,31 +922,43 @@ def test_random_layout_generated_recipes(self): only_onions_dict_recipes = [r.to_dict() for r in only_onions_recipes] # checking if recipes are generated from mdp_params - mdp_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}} - mdp_gen_params = {"mdp_params": mdp_params} - mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + mdp_gen_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, + "prop_feats": 0.9, + "prop_empty": 0.1, + "inner_shape": (6, 5), + "display": False} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6, 5)) env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) for _ in range(10): env.reset() self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) - self.assertTrue(len(env.mdp.start_bonus_orders) == 0) + self.assertEqual(len(env.mdp.start_bonus_orders), 0) # checking if bonus_orders is subset of all_orders even if not specified - mdp_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, - "generate_bonus_orders": {"n":1, "min_size":2, "max_size":3}} - mdp_gen_params = {"mdp_params": mdp_params} - mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + + mdp_gen_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, + "generate_bonus_orders": {"n":1, "min_size":2, "max_size":3}, + "prop_feats": 0.9, + "prop_empty": 0.1, + "inner_shape": (6, 5), + "display": False} + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6,5)) env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) for _ in range(10): env.reset() self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) - self.assertTrue(len(env.mdp.start_bonus_orders) == 1) + self.assertEqual(len(env.mdp.start_bonus_orders), 1) self.assertTrue(env.mdp.start_bonus_orders[0] in only_onions_dict_recipes) # checking if after reset there are new recipes generated - mdp_params = {"generate_all_orders": {"n":3, "min_size":2, "max_size":3}} - mdp_gen_params = {"mdp_params": mdp_params} - mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(**mdp_gen_params) + mdp_gen_params = {"generate_all_orders": {"n":3, "min_size":2, "max_size":3}, + "prop_feats": 0.9, + "prop_empty": 0.1, + "inner_shape": (6, 5), + "display": False, + "feature_types": [POT, DISH_DISPENSER, SERVING_LOC, ONION_DISPENSER, TOMATO_DISPENSER] + } + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6,5)) env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) generated_recipes_strings = set() for _ in range(20):