|
1 | 1 | import unittest, os
|
| 2 | +import json |
2 | 3 | import numpy as np
|
3 | 4 | from math import factorial
|
4 | 5 | from overcooked_ai_py.mdp.actions import Action, Direction
|
5 | 6 | from overcooked_ai_py.mdp.overcooked_mdp import PlayerState, OvercookedGridworld, OvercookedState, ObjectState, SoupState, Recipe
|
6 | 7 | from overcooked_ai_py.mdp.overcooked_env import OvercookedEnv, DEFAULT_ENV_PARAMS
|
7 |
| -from overcooked_ai_py.mdp.layout_generator import LayoutGenerator |
| 8 | +from overcooked_ai_py.mdp.layout_generator import LayoutGenerator, ONION_DISPENSER, TOMATO_DISPENSER, POT, DISH_DISPENSER, SERVING_LOC |
8 | 9 | from overcooked_ai_py.agents.agent import AgentGroup, AgentPair, GreedyHumanModel, FixedPlanAgent, RandomAgent
|
9 | 10 | from overcooked_ai_py.agents.benchmarking import AgentEvaluator
|
10 | 11 | from overcooked_ai_py.planning.planners import MediumLevelActionManager, NO_COUNTERS_PARAMS, MotionPlanner
|
11 | 12 | from overcooked_ai_py.utils import save_pickle, load_pickle, iterate_over_json_files_in_dir, load_from_json, save_as_json
|
12 | 13 | from utils import TESTING_DATA_DIR, generate_serialized_trajectory
|
13 | 14 |
|
14 |
| - |
15 | 15 | START_ORDER_LIST = ["any"]
|
16 | 16 | n, s = Direction.NORTH, Direction.SOUTH
|
17 | 17 | e, w = Direction.EAST, Direction.WEST
|
@@ -80,6 +80,24 @@ def test_invalid_input(self):
|
80 | 80 | self.assertRaises(ValueError, Recipe, [])
|
81 | 81 | self.assertRaises(ValueError, Recipe, "invalid argument")
|
82 | 82 |
|
| 83 | + def test_recipes_generation(self): |
| 84 | + self.assertRaises(AssertionError, Recipe.generate_random_recipes, max_size=Recipe.MAX_NUM_INGREDIENTS+1) |
| 85 | + self.assertRaises(AssertionError, Recipe.generate_random_recipes, min_size=0) |
| 86 | + self.assertRaises(AssertionError, Recipe.generate_random_recipes, min_size=3, max_size=2) |
| 87 | + self.assertRaises(AssertionError, Recipe.generate_random_recipes, ingredients=["onion", "tomato", "fake_ingredient"]) |
| 88 | + self.assertRaises(AssertionError, Recipe.generate_random_recipes, n=99999) |
| 89 | + self.assertEqual(len(Recipe.generate_random_recipes(n=3)), 3) |
| 90 | + self.assertEqual(len(Recipe.generate_random_recipes(n=99, unique=False)), 99) |
| 91 | + |
| 92 | + two_sized_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "tomato"]), Recipe(["tomato", "tomato"])] |
| 93 | + for _ in range(100): |
| 94 | + self.assertCountEqual(two_sized_recipes, Recipe.generate_random_recipes(n=3, min_size=2, max_size=2, ingredients=["onion", "tomato"])) |
| 95 | + |
| 96 | + only_onions_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "onion", "onion"])] |
| 97 | + for _ in range(100): |
| 98 | + self.assertCountEqual(only_onions_recipes, Recipe.generate_random_recipes(n=2, min_size=2, max_size=3, ingredients=["onion"])) |
| 99 | + |
| 100 | + 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 |
83 | 101 |
|
84 | 102 | def _expected_num_recipes(self, num_ingredients, max_len):
|
85 | 103 | return comb(num_ingredients + max_len, num_ingredients) - 1
|
@@ -873,6 +891,81 @@ def test_random_layout(self):
|
873 | 891 | all_same_layout = all([np.array_equal(env.mdp.terrain_mtx, terrain) for terrain in layouts_seen])
|
874 | 892 | self.assertFalse(all_same_layout)
|
875 | 893 |
|
| 894 | + def test_random_layout_feature_types(self): |
| 895 | + mandatory_features = {POT, DISH_DISPENSER, SERVING_LOC} |
| 896 | + optional_features = {ONION_DISPENSER, TOMATO_DISPENSER} |
| 897 | + optional_features_combinations = [{ONION_DISPENSER, TOMATO_DISPENSER}, {ONION_DISPENSER}, {TOMATO_DISPENSER}] |
| 898 | + |
| 899 | + for optional_features_combo in optional_features_combinations: |
| 900 | + left_out_optional_features = optional_features - optional_features_combo |
| 901 | + used_features = list(optional_features_combo | mandatory_features) |
| 902 | + mdp_gen_params = {"prop_feats": 0.9, |
| 903 | + "feature_types": used_features, |
| 904 | + "prop_empty": 0.1, |
| 905 | + "inner_shape": (6, 5), |
| 906 | + "display": False, |
| 907 | + "start_all_orders" : [ |
| 908 | + { "ingredients" : ["onion", "onion", "onion"]} |
| 909 | + ]} |
| 910 | + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6, 5)) |
| 911 | + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) |
| 912 | + for _ in range(10): |
| 913 | + env.reset() |
| 914 | + curr_terrain = env.mdp.terrain_mtx |
| 915 | + terrain_features = set.union(*(set(line) for line in curr_terrain)) |
| 916 | + self.assertTrue(all(elem in terrain_features for elem in used_features)) # all used_features are actually used |
| 917 | + if left_out_optional_features: |
| 918 | + self.assertFalse(any(elem in terrain_features for elem in left_out_optional_features)) # all left_out optional_features are not used |
| 919 | + |
| 920 | + def test_random_layout_generated_recipes(self): |
| 921 | + only_onions_recipes = [Recipe(["onion", "onion"]), Recipe(["onion", "onion", "onion"])] |
| 922 | + only_onions_dict_recipes = [r.to_dict() for r in only_onions_recipes] |
| 923 | + |
| 924 | + # checking if recipes are generated from mdp_params |
| 925 | + mdp_gen_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, |
| 926 | + "prop_feats": 0.9, |
| 927 | + "prop_empty": 0.1, |
| 928 | + "inner_shape": (6, 5), |
| 929 | + "display": False} |
| 930 | + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6, 5)) |
| 931 | + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) |
| 932 | + for _ in range(10): |
| 933 | + env.reset() |
| 934 | + self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) |
| 935 | + self.assertEqual(len(env.mdp.start_bonus_orders), 0) |
| 936 | + |
| 937 | + # checking if bonus_orders is subset of all_orders even if not specified |
| 938 | + |
| 939 | + mdp_gen_params = {"generate_all_orders": {"n":2, "ingredients": ["onion"], "min_size":2, "max_size":3}, |
| 940 | + "generate_bonus_orders": {"n":1, "min_size":2, "max_size":3}, |
| 941 | + "prop_feats": 0.9, |
| 942 | + "prop_empty": 0.1, |
| 943 | + "inner_shape": (6, 5), |
| 944 | + "display": False} |
| 945 | + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6,5)) |
| 946 | + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) |
| 947 | + for _ in range(10): |
| 948 | + env.reset() |
| 949 | + self.assertCountEqual(env.mdp.start_all_orders, only_onions_dict_recipes) |
| 950 | + self.assertEqual(len(env.mdp.start_bonus_orders), 1) |
| 951 | + self.assertTrue(env.mdp.start_bonus_orders[0] in only_onions_dict_recipes) |
| 952 | + |
| 953 | + # checking if after reset there are new recipes generated |
| 954 | + mdp_gen_params = {"generate_all_orders": {"n":3, "min_size":2, "max_size":3}, |
| 955 | + "prop_feats": 0.9, |
| 956 | + "prop_empty": 0.1, |
| 957 | + "inner_shape": (6, 5), |
| 958 | + "display": False, |
| 959 | + "feature_types": [POT, DISH_DISPENSER, SERVING_LOC, ONION_DISPENSER, TOMATO_DISPENSER] |
| 960 | + } |
| 961 | + mdp_fn = LayoutGenerator.mdp_gen_fn_from_dict(mdp_gen_params, outer_shape=(6,5)) |
| 962 | + env = OvercookedEnv(mdp_fn, **DEFAULT_ENV_PARAMS) |
| 963 | + generated_recipes_strings = set() |
| 964 | + for _ in range(20): |
| 965 | + env.reset() |
| 966 | + generated_recipes_strings |= {json.dumps(o, sort_keys=True) for o in env.mdp.start_all_orders} |
| 967 | + self.assertTrue(len(generated_recipes_strings) > 3) |
| 968 | + |
876 | 969 |
|
877 | 970 | class TestGymEnvironment(unittest.TestCase):
|
878 | 971 |
|
|
0 commit comments