From c3ef91b9c0673c429bca252271569198e399d263 Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Thu, 3 Oct 2024 01:54:15 -0700 Subject: [PATCH] Add seed parameter to all simulations, propagating it into sample_parameters PiperOrigin-RevId: 681777282 Change-Id: Ic3b04f860840e8f3653a977647e97f2aaa0f45ff --- concordia/environment/game_master.py | 13 ++- concordia/environment/scenes/conversation.py | 14 ++- .../factory/environment/basic_game_master.py | 5 + examples/modular/environment/haggling.py | 3 + .../environment/haggling_multi_item.py | 3 + .../environment/labor_collective_action.py | 45 +++++--- .../circa_2015_british_reality_show.py | 24 ++-- ...ality_show__prisoners_dilemma_3_players.py | 3 +- .../early_2000s_american_reality_show.py | 23 ++-- ...merican_reality_show__chicken_3_players.py | 3 +- ...merican_reality_show__chicken_4_players.py | 3 +- ...ality_show__prisoners_dilemma_3_players.py | 3 +- ...ality_show__prisoners_dilemma_4_players.py | 3 +- ...rican_reality_show__stag_hunt_3_players.py | 3 +- ...rican_reality_show__stag_hunt_4_players.py | 3 +- .../modules/garment_factory_labor.py | 44 +++++--- .../wild_west_railroad_construction_labor.py | 105 +++++++++++------- .../modular/environment/pub_coordination.py | 3 + examples/modular/environment/reality_show.py | 27 +++-- .../environment/utils/helper_functions.py | 11 +- examples/modular/scenario/scenarios.py | 2 + 21 files changed, 223 insertions(+), 120 deletions(-) diff --git a/concordia/environment/game_master.py b/concordia/environment/game_master.py index 583a81a2..3833c9cf 100644 --- a/concordia/environment/game_master.py +++ b/concordia/environment/game_master.py @@ -31,9 +31,9 @@ from concordia.typing import component from concordia.utils import concurrency from concordia.utils import helper_functions +import numpy as np import termcolor - DEFAULT_THOUGHTS = ( thought_chains.attempt_to_result, thought_chains.result_to_who_what_where, @@ -97,6 +97,7 @@ def __init__( concurrent_externalities: bool = True, use_default_instructions: bool = True, log_color: str = 'red', + seed: int | None = None, ): """Game master constructor. @@ -123,6 +124,7 @@ def __init__( instructions used for the game master, e.g. do this if you plan to pass custom instructions as a constant component instead. log_color: color in which to print logs + seed: random seed for the game master """ self._name = name self._model = model @@ -132,6 +134,9 @@ def __init__( self._randomise_initiative = randomise_initiative self._player_observes_event = player_observes_event self._players_act_simultaneously = players_act_simultaneously + self._seed = seed or random.getrandbits(63) + self._rng = random.Random(seed) + if isinstance(action_spec, agent_lib.ActionSpec): self._action_spec = {player.name: action_spec for player in players} else: @@ -201,7 +206,9 @@ def _handle_action(self, player_name: str, action_attempt: str) -> None: }) # Produce the event that has happened as the result of the action attempt - prompt = interactive_document.InteractiveDocument(self._model) + prompt = interactive_document.InteractiveDocument( + self._model, rng=np.random.default_rng(self._seed) + ) for comp in self._components.values(): state_of_component = comp.state() if state_of_component: @@ -324,7 +331,7 @@ def step( else: players = list(self._players_by_name.values()) if self._randomise_initiative: - random.shuffle(players) + self._rng.shuffle(players) if action_spec_override is None: action_spec = self._action_spec diff --git a/concordia/environment/scenes/conversation.py b/concordia/environment/scenes/conversation.py index 9f6496c6..1f5ec1fa 100644 --- a/concordia/environment/scenes/conversation.py +++ b/concordia/environment/scenes/conversation.py @@ -20,6 +20,7 @@ """ from collections.abc import Sequence +import random from concordia.agents import deprecated_agent from concordia.associative_memory import blank_memories @@ -31,6 +32,7 @@ from concordia.typing import agent as simulacrum_agent from concordia.typing import component from concordia.typing import entity +import numpy as np import termcolor @@ -47,6 +49,7 @@ def __init__( max_steps: int | None = None, verbose: bool = False, log_colour: str = 'red', + seed: int | None = None, ): """This component accumulates history of a conversation scene in its state. @@ -62,6 +65,7 @@ def __init__( max_steps: Maximum number of conversation steps. If none, no limit verbose: whether or not to print intermediate reasoning steps log_colour: colour for logging + seed: random seed for the chain of thought document """ self._model = model self._state = premise @@ -71,7 +75,7 @@ def __init__( self._key_question = key_question self._max_steps = max_steps self._current_steps = 0 - + self._seed = seed or random.getrandbits(63) self._verbose = verbose def name(self) -> str: @@ -87,7 +91,9 @@ def terminate_episode(self) -> bool: if not self._check_for_termination: return False - chain_of_thought = interactive_document.InteractiveDocument(self._model) + chain_of_thought = interactive_document.InteractiveDocument( + self._model, rng=np.random.default_rng(self._seed) + ) chain_of_thought.statement('\n') chain_of_thought.statement(f'Key question: {self._key_question}') chain_of_thought.statement(f'Conversation:\n{self._state}\n') @@ -154,6 +160,7 @@ def make_conversation_game_master( key_question: str | None = None, max_steps: int | None = 3, verbose: bool = False, + seed: int | None = None, ): """Creates a game master that runs a conversation between players. @@ -176,6 +183,7 @@ def make_conversation_game_master( answer to this question. max_steps: Maximum number of conversation steps. If none, no limit verbose: whether or not to print + seed: random seed for the game master Returns: a game master @@ -211,6 +219,7 @@ def make_conversation_game_master( check_for_termination=check_for_termination, key_question=key_question, max_steps=max_steps, + seed=seed, ) for player in players: @@ -230,5 +239,6 @@ def make_conversation_game_master( player_observes_event=False, concurrent_externalities=False, verbose=True, + seed=seed, ) return game_master diff --git a/concordia/factory/environment/basic_game_master.py b/concordia/factory/environment/basic_game_master.py index 1079742d..81894deb 100644 --- a/concordia/factory/environment/basic_game_master.py +++ b/concordia/factory/environment/basic_game_master.py @@ -59,6 +59,7 @@ def build_game_master( npc_context: str = '', max_conversation_length: int = 10, verbose: bool = False, + seed: int | None = None, ) -> tuple[game_master.GameMaster, associative_memory.AssociativeMemory]: """Build a game master (i.e., an environment). @@ -84,6 +85,7 @@ def build_game_master( npc_context: extra context provided only to non-player characters max_conversation_length: The maximum number of turns in a conversation. verbose: whether or not to print verbose debug information + seed: random seed for the chain of thought document Returns: A tuple consisting of a game master and its memory. @@ -178,6 +180,7 @@ def build_game_master( ], randomise_initiative=True, player_observes_event=False, + seed=seed, verbose=verbose, ) @@ -192,6 +195,7 @@ def build_decision_scene_game_master( decision_action_spec: agent_lib.ActionSpec, payoffs: gm_components.schelling_diagram_payoffs.SchellingPayoffs, verbose: bool = False, + seed: int | None = None, ) -> game_master.GameMaster: """Build a decision game master for decision scenes.""" decision_env = game_master.GameMaster( @@ -207,6 +211,7 @@ def build_decision_scene_game_master( player_observes_event=False, concurrent_externalities=False, verbose=verbose, + seed=seed, ) return decision_env diff --git a/examples/modular/environment/haggling.py b/examples/modular/environment/haggling.py index d69ff497..18cc2f61 100644 --- a/examples/modular/environment/haggling.py +++ b/examples/modular/environment/haggling.py @@ -552,6 +552,7 @@ def __init__( only_match_with_support: bool = False, num_games: int = 2, num_main_players: int = 3, + seed: int | None = None, ): """Initialize the simulation object. @@ -580,6 +581,7 @@ def __init__( supporting players. num_games: the number of games to play. num_main_players: the number of main players. + seed: the random seed to use. """ # Support for these parameters will be added in a future addition coming # very imminently. @@ -589,6 +591,7 @@ def __init__( helper_functions.load_time_and_place_module( time_and_place_module=time_and_place_module, default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES, + seed=seed, ) ) sampled_settings.num_supporting_players = num_supporting_player diff --git a/examples/modular/environment/haggling_multi_item.py b/examples/modular/environment/haggling_multi_item.py index 87f68de2..c5f9a887 100644 --- a/examples/modular/environment/haggling_multi_item.py +++ b/examples/modular/environment/haggling_multi_item.py @@ -582,6 +582,7 @@ def __init__( only_match_with_support: bool = False, num_games: int = 2, num_main_players: int = 3, + seed: int | None = None, ): """Initialize the simulation object. @@ -610,6 +611,7 @@ def __init__( supporting players. num_games: the number of games to play. num_main_players: the number of main players. + seed: the random seed to use. """ # Support for these parameters will be added in a future addition coming # very imminently. @@ -619,6 +621,7 @@ def __init__( helper_functions.load_time_and_place_module( time_and_place_module=time_and_place_module, default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES, + seed=seed, ) ) sampled_settings.num_supporting_players = num_supporting_player diff --git a/examples/modular/environment/labor_collective_action.py b/examples/modular/environment/labor_collective_action.py index 1c196195..1821ff8f 100644 --- a/examples/modular/environment/labor_collective_action.py +++ b/examples/modular/environment/labor_collective_action.py @@ -123,6 +123,7 @@ def configure_players( model: language_model.LanguageModel, sampled_settings: Any, time_and_place_params: types.ModuleType, + rng: random.Random, ) -> tuple[ list[formative_memories.AgentConfig], list[formative_memories.AgentConfig], @@ -136,6 +137,7 @@ def configure_players( sampled_settings: the environment configuration containing the time and place details. time_and_place_params: the module containing the time and place parameters + rng: the random number generator to use. Returns: main_player_configs: configs for the main characters @@ -161,9 +163,9 @@ def get_agent_config(player_name: str, environment_cfg: Any): subject_pronoun = 'they' object_pronoun = 'their' - birth_year = environment_cfg.year - (30 + random.randint(-8, 8)) - birth_month = random.randint(1, 12) - birth_day = random.randint(1, 28) + birth_year = environment_cfg.year - (30 + rng.randint(-8, 8)) + birth_month = rng.randint(1, 12) + birth_day = rng.randint(1, 28) goal_str = ( f'{player_name} hopes to be able to provide for their ' 'family and live a full life.' @@ -172,7 +174,9 @@ def get_agent_config(player_name: str, environment_cfg: Any): f"{player_name}'s personality is like " + player_traits_and_styles.get_trait(flowery=True) ) - prompt = interactive_document.InteractiveDocument(model) + prompt = interactive_document.InteractiveDocument( + model, rng=np.random.default_rng(sampled_settings.seed) + ) prompt.statement( 'The following exercise is preparatory work for a role playing ' 'session. The purpose of the exercise is to fill in the backstory ' @@ -271,9 +275,9 @@ def get_agent_config(player_name: str, environment_cfg: Any): 'gender', None ), date_of_birth=datetime.datetime( - year=sampled_settings.year - (30 + random.randint(10, 30)), - month=random.randint(1, 12), - day=random.randint(1, 28), + year=sampled_settings.year - (30 + rng.randint(10, 30)), + month=rng.randint(1, 12), + day=rng.randint(1, 28), ), goal=( f'{sampled_settings.antagonist} wants to make as much money ' @@ -307,9 +311,9 @@ def get_agent_config(player_name: str, environment_cfg: Any): 'gender', None ), date_of_birth=datetime.datetime( - year=sampled_settings.year - (30 + random.randint(2, 10)), - month=random.randint(1, 12), - day=random.randint(1, 28), + year=sampled_settings.year - (30 + rng.randint(2, 10)), + month=rng.randint(1, 12), + day=rng.randint(1, 28), ), goal=( f'{sampled_settings.organizer} wants to prevent the ' @@ -424,6 +428,7 @@ def configure_scenes( player_observes_event=False, concurrent_externalities=False, verbose=verbose, + seed=sampled_settings.seed, ) def _get_discussion_scene_type( @@ -644,6 +649,7 @@ def __init__( bots_lib.SupportingAgentFactory | types.ModuleType ) = rational_agent_supporting, time_and_place_module: str | None = None, + seed: int | None = None, ): """Initialize the simulation object. @@ -665,6 +671,7 @@ def __init__( time_and_place_module: optionally, specify a module containing settings that create a sense of setting in a specific time and place. If not specified, a random module will be chosen from the default options. + seed: the random seed to use. """ if resident_visitor_modules is None: self._resident_visitor_mode = False @@ -674,7 +681,6 @@ def __init__( self._resident_agent_module, self._visitor_agent_module = ( resident_visitor_modules ) - self._agent_model = model if override_agent_model: @@ -690,9 +696,12 @@ def __init__( time_and_place_params, sampled_settings = ( helper_functions.load_time_and_place_module( time_and_place_module=time_and_place_module, - default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES) + default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES, + seed=seed, + ) ) + self._rng = random.Random(sampled_settings.seed) start_time = datetime.datetime( year=time_and_place_params.YEAR, month=time_and_place_params.MONTH, @@ -735,11 +744,14 @@ def __init__( ) main_player_configs, supporting_player_configs, antagonist_config, _ = ( - configure_players(model=model, - sampled_settings=sampled_settings, - time_and_place_params=time_and_place_params) + configure_players( + model=model, + sampled_settings=sampled_settings, + time_and_place_params=time_and_place_params, + rng=self._rng, + ) ) - random.shuffle(main_player_configs) + self._rng.shuffle(main_player_configs) tasks = { config.name: functools.partial( @@ -989,6 +1001,7 @@ def set_wage_function(args: _TriggeredFunctionPreEventFnArgsT) -> str: sampled_settings.supporting_player_locations ), additional_components=additional_gm_components, + seed=seed, ) ) self._scenes, decision_env, industrial_action = configure_scenes( diff --git a/examples/modular/environment/modules/circa_2015_british_reality_show.py b/examples/modular/environment/modules/circa_2015_british_reality_show.py index 4607f4be..b91752ef 100644 --- a/examples/modular/environment/modules/circa_2015_british_reality_show.py +++ b/examples/modular/environment/modules/circa_2015_british_reality_show.py @@ -462,30 +462,35 @@ def sample_parameters( - minigame_name: str = DEFAULT_MINIGAME, num_players: int | None = None + minigame_name: str = DEFAULT_MINIGAME, + num_players: int | None = None, + seed: int | None = None, ) -> reality_show.WorldConfig: """Sample parameters of the setting and the backstory for each player.""" - shuffled_male_names = list(random.sample(MALE_NAMES, len(MALE_NAMES))) - shuffled_female_names = list(random.sample(FEMALE_NAMES, len(FEMALE_NAMES))) + seed = seed or random.getrandbits(63) + rng = random.Random(seed) + + shuffled_male_names = list(rng.sample(MALE_NAMES, len(MALE_NAMES))) + shuffled_female_names = list(rng.sample(FEMALE_NAMES, len(FEMALE_NAMES))) if num_players is None: - num_players = random.choice(DEFAULT_POSSIBLE_NUM_PLAYERS) + num_players = rng.choice(DEFAULT_POSSIBLE_NUM_PLAYERS) contestants = {} for _ in range(num_players): - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': player_name = shuffled_male_names.pop() - stereotype = random.choice(MALE_TYPE_CASTS) + stereotype = rng.choice(MALE_TYPE_CASTS) else: player_name = shuffled_female_names.pop() - stereotype = random.choice(FEMALE_TYPE_CASTS) - interview_questions = random.sample( + stereotype = rng.choice(FEMALE_TYPE_CASTS) + interview_questions = rng.sample( BRITSH_STEREOTYPED_CHARACTERS[stereotype]['interview_questions'], NUM_INTERVIEW_QUESTIONS, ) contestants[player_name] = { 'gender': gender, 'traits': BRITSH_STEREOTYPED_CHARACTERS[stereotype]['traits'], - 'catchphrase': random.choice( + 'catchphrase': rng.choice( BRITSH_STEREOTYPED_CHARACTERS[stereotype]['catchphrases'] ), 'interview_questions': interview_questions, @@ -501,4 +506,5 @@ def sample_parameters( num_players=num_players, contestants=contestants, num_minigame_reps_per_scene=NUM_MINIGAME_REPS_PER_SCENE, + seed=seed, ) diff --git a/examples/modular/environment/modules/circa_2015_british_reality_show__prisoners_dilemma_3_players.py b/examples/modular/environment/modules/circa_2015_british_reality_show__prisoners_dilemma_3_players.py index 422a7de0..93dfb7d6 100644 --- a/examples/modular/environment/modules/circa_2015_british_reality_show__prisoners_dilemma_3_players.py +++ b/examples/modular/environment/modules/circa_2015_british_reality_show__prisoners_dilemma_3_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import circa_2015_british_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='prisoners_dilemma', num_players=3, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show.py b/examples/modular/environment/modules/early_2000s_american_reality_show.py index 68c511d6..21e22c37 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show.py @@ -725,30 +725,34 @@ def sample_parameters( - minigame_name: str = DEFAULT_MINIGAME, num_players: int | None = None + minigame_name: str = DEFAULT_MINIGAME, + num_players: int | None = None, + seed: int | None = None, ) -> reality_show.WorldConfig: """Sample parameters of the setting and the backstory for each player.""" - shuffled_male_names = list(random.sample(MALE_NAMES, len(MALE_NAMES))) - shuffled_female_names = list(random.sample(FEMALE_NAMES, len(FEMALE_NAMES))) + seed = seed or random.getrandbits(63) + rng = random.Random(seed) + shuffled_male_names = list(rng.sample(MALE_NAMES, len(MALE_NAMES))) + shuffled_female_names = list(rng.sample(FEMALE_NAMES, len(FEMALE_NAMES))) if num_players is None: - num_players = random.choice(DEFAULT_POSSIBLE_NUM_PLAYERS) + num_players = rng.choice(DEFAULT_POSSIBLE_NUM_PLAYERS) contestants = {} for _ in range(num_players): - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': player_name = shuffled_male_names.pop() - stereotype = random.choice(EARLY_2000_MALE_TYPE_CASTS) + stereotype = rng.choice(EARLY_2000_MALE_TYPE_CASTS) else: player_name = shuffled_female_names.pop() - stereotype = random.choice(EARLY_2000_FEMALE_TYPE_CASTS) - interview_questions = random.sample( + stereotype = rng.choice(EARLY_2000_FEMALE_TYPE_CASTS) + interview_questions = rng.sample( EARLY_2000_STEREOTYPED_CHARACTERS[stereotype]['interview_questions'], NUM_INTERVIEW_QUESTIONS, ) contestants[player_name] = { 'gender': gender, 'traits': EARLY_2000_STEREOTYPED_CHARACTERS[stereotype]['traits'], - 'catchphrase': random.choice( + 'catchphrase': rng.choice( EARLY_2000_STEREOTYPED_CHARACTERS[stereotype]['catchphrases'] ), 'interview_questions': interview_questions, @@ -764,4 +768,5 @@ def sample_parameters( num_players=num_players, contestants=contestants, num_minigame_reps_per_scene=NUM_MINIGAME_REPS_PER_SCENE, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_3_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_3_players.py index c91443f6..be89e07f 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_3_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_3_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='chicken', num_players=3, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_4_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_4_players.py index a06b2abe..0ff31c9b 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_4_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__chicken_4_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='chicken', num_players=4, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_3_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_3_players.py index 5c048a8e..8db1373f 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_3_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_3_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='prisoners_dilemma', num_players=3, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_4_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_4_players.py index 18ac190d..30c0d817 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_4_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__prisoners_dilemma_4_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='prisoners_dilemma', num_players=4, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_3_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_3_players.py index 1c99002e..f9f644b4 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_3_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_3_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='stag_hunt', num_players=3, + seed=seed, ) diff --git a/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_4_players.py b/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_4_players.py index 3b49eaf7..0fa314d5 100644 --- a/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_4_players.py +++ b/examples/modular/environment/modules/early_2000s_american_reality_show__stag_hunt_4_players.py @@ -18,9 +18,10 @@ from examples.modular.environment.modules import early_2000s_american_reality_show as parent_module -def sample_parameters(): +def sample_parameters(seed: int | None = None): """Sample parameters of the setting and the backstory for each player.""" return parent_module.sample_parameters( minigame_name='stag_hunt', num_players=4, + seed=seed, ) diff --git a/examples/modular/environment/modules/garment_factory_labor.py b/examples/modular/environment/modules/garment_factory_labor.py index b70a0389..e592a7e7 100644 --- a/examples/modular/environment/modules/garment_factory_labor.py +++ b/examples/modular/environment/modules/garment_factory_labor.py @@ -926,6 +926,7 @@ def _details_generator( antagonist_gender: str, organizer_name: str, organizer_gender: str, + rng: random.Random, ) -> dict[str, str | None]: """Fill in details of the characters and their world.""" generated = {str(key): '' for key in extract_braces(element_string)} @@ -942,9 +943,9 @@ def _details_generator( continue else: if key == 'neighborhood_name': - generated[key] = random.choice(FACTORY_NEIGHBORHOOD_NAMES) + generated[key] = rng.choice(FACTORY_NEIGHBORHOOD_NAMES) if key == 'street_name': - generated[key] = random.choice(STREET_NAMES) + generated[key] = rng.choice(STREET_NAMES) if key == 'factory_name': generated[key] = factory_name if key == 'antagonist_name': @@ -959,10 +960,13 @@ def _details_generator( def sample_parameters( num_flavor_prompts_per_player: int = DEFAULT_NUM_FLAVOR_PROMPTS, + seed: int | None = None, ): """Sample parameters of the setting and the backstory for each player.""" + seed = seed or random.getrandbits(63) + rng = random.Random(seed) poor_work_conditions = tuple( - random.sample( + rng.sample( BAD_GARMENT_WORK_CONDITIONS, DEFAULT_NUM_BACKGROUND_BAD_CONDITIONS ) ) @@ -972,19 +976,19 @@ def sample_parameters( background_poor_work_conditions=poor_work_conditions, ) - shuffled_male_names = list(random.sample(MALE_NAMES, len(MALE_NAMES))) - shuffled_female_names = list(random.sample(FEMALE_NAMES, len(FEMALE_NAMES))) + shuffled_male_names = list(rng.sample(MALE_NAMES, len(MALE_NAMES))) + shuffled_female_names = list(rng.sample(FEMALE_NAMES, len(FEMALE_NAMES))) - sampled_railroad_name = random.choice(FACTORY_NAMES) + sampled_railroad_name = rng.choice(FACTORY_NAMES) - sampled_antagonist_gender = random.choice(GENDERS) + sampled_antagonist_gender = rng.choice(GENDERS) if sampled_antagonist_gender == 'male': sampled_antagonist_name = shuffled_male_names.pop() else: sampled_antagonist_name = shuffled_female_names.pop() config.antagonist = sampled_antagonist_name - sampled_organizer_gender = random.choice(GENDERS) + sampled_organizer_gender = rng.choice(GENDERS) if sampled_organizer_gender == 'male': sampled_organizer_name = shuffled_male_names.pop() else: @@ -992,7 +996,7 @@ def sample_parameters( config.organizer = sampled_organizer_name shuffled_talking_points = list( - random.sample( + rng.sample( OVERHEARD_ORGANIZER_TALKING_POINTS, len(OVERHEARD_ORGANIZER_TALKING_POINTS), ) @@ -1005,16 +1009,16 @@ def sample_parameters( ] world_elements = list( - random.sample(WORLD_BUILDING_ELEMENTS, NUM_WORLD_BUILDING_ELEMENTS) + rng.sample(WORLD_BUILDING_ELEMENTS, NUM_WORLD_BUILDING_ELEMENTS) ) railroad_worker_elements = list( - random.sample(GARMENT_WORKER_BIOS, NUM_GARMENT_WORKER_ELEMENTS) + rng.sample(GARMENT_WORKER_BIOS, NUM_GARMENT_WORKER_ELEMENTS) ) antagonist_elements = list( - random.sample(ANTAGONIST_ELEMENTS, NUM_ANTAGONIST_ELEMENTS) + rng.sample(ANTAGONIST_ELEMENTS, NUM_ANTAGONIST_ELEMENTS) ) organizer_rumors = list( - random.sample(LABOR_ORGANIZER_RUMORS, NUM_ORGANIZER_RUMORS) + rng.sample(LABOR_ORGANIZER_RUMORS, NUM_ORGANIZER_RUMORS) ) formatted_world_elements = [] @@ -1029,20 +1033,20 @@ def sample_parameters( gender = None if 'worker_name' in extract_braces(element_string): # Instantiate a new character. - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': person_name = shuffled_male_names.pop() else: person_name = shuffled_female_names.pop() salient_poor_conditions = tuple( - random.sample( + rng.sample( poor_work_conditions, DEFAULT_NUM_SALIENT_POOR_WORK_CONDITIONS ) ) config.append_person(person_name, gender, salient_poor_conditions) formative_memory_prompts[person_name] = [] protagonist_backstory_elements = list( - random.sample( + rng.sample( PROTAGONIST_BACKSTORY_FLAVOR_PROMPTS, num_flavor_prompts_per_player, ) @@ -1058,6 +1062,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) protagonist_generated['worker_name'] = person_name _add_pronouns(protagonist_generated, gender=gender) @@ -1074,6 +1079,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) formatted_world_elements.append(element_string.format(**generated)) @@ -1088,6 +1094,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) antagonist_own_memories.append(element_string.format(**generated)) organizer_own_memories = [] @@ -1101,6 +1108,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) organizer_own_memories.append(element_string.format(**generated)) @@ -1128,13 +1136,13 @@ def sample_parameters( if not config.people: # Handle unlikely case where no protagonists were generated. - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': person_name = shuffled_male_names.pop() else: person_name = shuffled_female_names.pop() salient_poor_conditions = tuple( - random.sample( + rng.sample( poor_work_conditions, DEFAULT_NUM_SALIENT_POOR_WORK_CONDITIONS ) ) diff --git a/examples/modular/environment/modules/wild_west_railroad_construction_labor.py b/examples/modular/environment/modules/wild_west_railroad_construction_labor.py index 6ca6a686..d508b167 100644 --- a/examples/modular/environment/modules/wild_west_railroad_construction_labor.py +++ b/examples/modular/environment/modules/wild_west_railroad_construction_labor.py @@ -990,18 +990,30 @@ ) OVERHEARD_ORGANIZER_TALKING_POINTS = ( - ('...from sea to shining sea, we\'ve built this nation\'s backbone. ' - 'Now we\'ll break our backs no more!'), - ('...our blood and sweat mix with the gravel beneath these rails. ' - 'It\'s time the company pays its due!'), - ('...they say we\'re replaceable, but let\'s see them drive a spike ' - 'or blast through mountain. Without us, the rails don\'t run!'), - ('...and that\'s why we should stop working till the boss ' - 'raises our wages! Let\'s show them we won\'t be taken advantage of!'), - ('...enough is enough! We won\'t be silenced, we won\'t be ignored. ' - 'It\'s time to walk out!'), - ('...we\'ve laid track from dawn to dusk, but our pockets are still ' - 'empty. It\'s time to halt these iron horses!'), + ( + "...from sea to shining sea, we've built this nation's backbone. " + "Now we'll break our backs no more!" + ), + ( + '...our blood and sweat mix with the gravel beneath these rails. ' + "It's time the company pays its due!" + ), + ( + "...they say we're replaceable, but let's see them drive a spike " + "or blast through mountain. Without us, the rails don't run!" + ), + ( + "...and that's why we should stop working till the boss " + "raises our wages! Let's show them we won't be taken advantage of!" + ), + ( + "...enough is enough! We won't be silenced, we won't be ignored. " + "It's time to walk out!" + ), + ( + "...we've laid track from dawn to dusk, but our pockets are still " + "empty. It's time to halt these iron horses!" + ), ) GENDERS = ('male', 'female') @@ -1033,15 +1045,13 @@ class WorldConfig: year: int location: str + seed: int background_poor_work_conditions: Sequence[str] world_elements: Sequence[str] = () people: Sequence[str] = () - person_data: dict[ - str, - dict[str, - Union[str, Sequence[str]] - ] - ] = dataclasses.field(default_factory=dict) + person_data: dict[str, dict[str, Union[str, Sequence[str]]]] = ( + dataclasses.field(default_factory=dict) + ) formative_memory_prompts: Mapping[str, Sequence[str]] | None = None antagonist: str | None = None organizer: str | None = None @@ -1086,6 +1096,7 @@ def _details_generator( antagonist_gender: str, organizer_name: str, organizer_gender: str, + rng: random.Random, ) -> dict[str, str | None]: """This function generates the details of the characters and their world.""" generated = {str(key): '' for key in extract_braces(element_string)} @@ -1102,21 +1113,21 @@ def _details_generator( continue else: if key == 'town_name': - generated[key] = random.choice(TOWN_NAMES) + generated[key] = rng.choice(TOWN_NAMES) if key == 'oasis_name': - generated[key] = random.choice(OASIS_NAMES) + generated[key] = rng.choice(OASIS_NAMES) if key == 'desert_name': - generated[key] = random.choice(DESERT_NAMES) + generated[key] = rng.choice(DESERT_NAMES) if key == 'plant_name': - generated[key] = random.choice(PLANT_NAMES) + generated[key] = rng.choice(PLANT_NAMES) if key == 'saloon_name': - generated[key] = random.choice(SALOON_NAMES) + generated[key] = rng.choice(SALOON_NAMES) if key == 'mesa_name': - generated[key] = random.choice(MESA_NAMES) + generated[key] = rng.choice(MESA_NAMES) if key == 'canyon_name': - generated[key] = random.choice(CANYON_NAMES) + generated[key] = rng.choice(CANYON_NAMES) if key == 'gang_name': - generated[key] = random.choice(GANG_NAMES) + generated[key] = rng.choice(GANG_NAMES) if key == 'railroad_name': generated[key] = railroad_name if key == 'antagonist_name': @@ -1131,11 +1142,14 @@ def _details_generator( def sample_parameters( num_flavor_prompts_per_player: int = DEFAULT_NUM_FLAVOR_PROMPTS, + seed: int | None = None, ): """Sample parameters of the setting and the backstory for each player.""" - nearby_town = random.choice(TOWN_NAMES) + seed = seed or random.getrandbits(63) + rng = random.Random(seed) + nearby_town = rng.choice(TOWN_NAMES) poor_work_conditions = tuple( - random.sample( + rng.sample( BAD_CONSTRUCTION_WORK_CONDITIONS, DEFAULT_NUM_BACKGROUND_BAD_CONDITIONS, ) @@ -1148,21 +1162,22 @@ def sample_parameters( f'settlement: {nearby_town}' ), background_poor_work_conditions=poor_work_conditions, + seed=seed, ) - shuffled_male_names = list(random.sample(MALE_NAMES, len(MALE_NAMES))) - shuffled_female_names = list(random.sample(FEMALE_NAMES, len(FEMALE_NAMES))) + shuffled_male_names = list(rng.sample(MALE_NAMES, len(MALE_NAMES))) + shuffled_female_names = list(rng.sample(FEMALE_NAMES, len(FEMALE_NAMES))) - sampled_railroad_name = random.choice(RAILROAD_NAMES) + sampled_railroad_name = rng.choice(RAILROAD_NAMES) - sampled_antagonist_gender = random.choice(GENDERS) + sampled_antagonist_gender = rng.choice(GENDERS) if sampled_antagonist_gender == 'male': sampled_antagonist_name = shuffled_male_names.pop() else: sampled_antagonist_name = shuffled_female_names.pop() config.antagonist = sampled_antagonist_name - sampled_organizer_gender = random.choice(GENDERS) + sampled_organizer_gender = rng.choice(GENDERS) if sampled_organizer_gender == 'male': sampled_organizer_name = shuffled_male_names.pop() else: @@ -1170,7 +1185,7 @@ def sample_parameters( config.organizer = sampled_organizer_name shuffled_talking_points = list( - random.sample( + rng.sample( OVERHEARD_ORGANIZER_TALKING_POINTS, len(OVERHEARD_ORGANIZER_TALKING_POINTS), ) @@ -1183,16 +1198,16 @@ def sample_parameters( ] world_elements = list( - random.sample(WORLD_BUILDING_ELEMENTS, NUM_WORLD_BUILDING_ELEMENTS) + rng.sample(WORLD_BUILDING_ELEMENTS, NUM_WORLD_BUILDING_ELEMENTS) ) railroad_worker_elements = list( - random.sample(RAILROAD_ELEMENTS, NUM_RAILROAD_WORKER_ELEMENTS) + rng.sample(RAILROAD_ELEMENTS, NUM_RAILROAD_WORKER_ELEMENTS) ) antagonist_elements = list( - random.sample(ANTAGONIST_ELEMENTS, NUM_ANTAGONIST_ELEMENTS) + rng.sample(ANTAGONIST_ELEMENTS, NUM_ANTAGONIST_ELEMENTS) ) organizer_rumors = list( - random.sample(LABOR_ORGANIZER_RUMORS, NUM_ORGANIZER_RUMORS) + rng.sample(LABOR_ORGANIZER_RUMORS, NUM_ORGANIZER_RUMORS) ) formatted_world_elements = [] @@ -1207,20 +1222,20 @@ def sample_parameters( gender = None if 'person_name' in extract_braces(element_string): # Instantiate a new character. - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': person_name = shuffled_male_names.pop() else: person_name = shuffled_female_names.pop() salient_poor_conditions = tuple( - random.sample( + rng.sample( poor_work_conditions, DEFAULT_NUM_SALIENT_POOR_WORK_CONDITIONS ) ) config.append_person(person_name, gender, salient_poor_conditions) formative_memory_prompts[person_name] = [] protagonist_backstory_elements = list( - random.sample( + rng.sample( PROTAGONIST_BACKSTORY_FLAVOR_PROMPTS, num_flavor_prompts_per_player, ) @@ -1236,6 +1251,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) protagonist_generated['person_name'] = person_name _add_pronouns(protagonist_generated, gender=gender) @@ -1252,6 +1268,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) formatted_world_elements.append(element_string.format(**generated)) @@ -1266,6 +1283,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) antagonist_own_memories.append(element_string.format(**generated)) organizer_own_memories = [] @@ -1279,6 +1297,7 @@ def sample_parameters( antagonist_gender=sampled_antagonist_gender, organizer_name=sampled_organizer_name, organizer_gender=sampled_organizer_gender, + rng=rng, ) organizer_own_memories.append(element_string.format(**generated)) @@ -1303,13 +1322,13 @@ def sample_parameters( if not config.people: # Handle unlikely case where no protagonists were generated. - gender = random.choice(GENDERS) + gender = rng.choice(GENDERS) if gender == 'male': person_name = shuffled_male_names.pop() else: person_name = shuffled_female_names.pop() salient_poor_conditions = tuple( - random.sample( + rng.sample( poor_work_conditions, DEFAULT_NUM_SALIENT_POOR_WORK_CONDITIONS ) ) diff --git a/examples/modular/environment/pub_coordination.py b/examples/modular/environment/pub_coordination.py index da5e7f8c..55883920 100644 --- a/examples/modular/environment/pub_coordination.py +++ b/examples/modular/environment/pub_coordination.py @@ -688,6 +688,7 @@ def __init__( num_games: int = 3, num_main_players: int = 4, num_supporting_players: int = 1, + seed: int | None = None, ): """Initialize the simulation object. @@ -717,6 +718,7 @@ def __init__( num_games: the number of games to play. num_main_players: the number of main players. num_supporting_players: the number of supporting players. + seed: the random seed to use. """ # Support for these parameters will be added in a future addition coming # very imminently. @@ -747,6 +749,7 @@ def __init__( helper_functions.load_time_and_place_module( time_and_place_module=time_and_place_module, default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES, + seed=seed, ) ) self._rng = random.Random(sampled_settings.random_seed) diff --git a/examples/modular/environment/reality_show.py b/examples/modular/environment/reality_show.py index 8bafee56..b6538c95 100644 --- a/examples/modular/environment/reality_show.py +++ b/examples/modular/environment/reality_show.py @@ -209,12 +209,13 @@ class WorldConfig: num_players: int contestants: Mapping[str, Mapping[str, Any]] num_minigame_reps_per_scene: tuple[int, ...] + seed: int -def get_random_show_with_description() -> tuple[str, str]: +def get_random_show_with_description(rng: random.Random) -> tuple[str, str]: """Randomly sample a show title and description from a prewritten list.""" - title = np.random.choice(CANDIDATE_SHOW_TITLES) - description = np.random.choice(CANDIDATE_SHOW_DESCRIPTIONS) + title = rng.choice(CANDIDATE_SHOW_TITLES) + description = rng.choice(CANDIDATE_SHOW_DESCRIPTIONS) return f'"{title}" is a reality TV show described as: "{description}"', title @@ -230,9 +231,10 @@ def _get_all_contestant_names_string(contestant_names: Sequence[str]): def get_shared_memories_and_context( model: language_model.LanguageModel, contestant_names: Sequence[str], + rng: random.Random, ) -> tuple[Sequence[str], str, str]: """Return the shared memories and context for all agents and game master.""" - show_title_and_description, show_title = get_random_show_with_description() + show_title_and_description, show_title = get_random_show_with_description(rng) all_contestants_string = _get_all_contestant_names_string(contestant_names) shared_memories = [ @@ -261,6 +263,7 @@ def configure_players( model: language_model.LanguageModel, show_title: str, sampled_settings: Any, + rng: random.Random, ) -> tuple[ list[formative_memories.AgentConfig], list[formative_memories.AgentConfig] ]: @@ -271,6 +274,7 @@ def configure_players( show_title: the name of the reality show. sampled_settings: the environment configuration containing the time and place details. + rng: the random number generator to use. Returns: main_player_configs: configs for the main characters @@ -278,9 +282,9 @@ def configure_players( """ def get_agent_config(player_name: str, sampled_settings: Any): - birth_year = sampled_settings.year - (25 + random.randint(-3, 3)) - birth_month = random.randint(1, 12) - birth_day = random.randint(1, 28) + birth_year = sampled_settings.year - (25 + rng.randint(-3, 3)) + birth_month = rng.randint(1, 12) + birth_day = rng.randint(1, 28) traits_str = sampled_settings.contestants[player_name]['traits'] catchphrase = sampled_settings.contestants[player_name]['catchphrase'] subject_pronoun = sampled_settings.contestants[player_name][ @@ -652,6 +656,7 @@ def __init__( resident_visitor_modules: Sequence[types.ModuleType] | None = None, supporting_agent_module: types.ModuleType | None = None, time_and_place_module: str | None = None, + seed: int | None = None, ): """Initialize the simulation object. @@ -675,6 +680,7 @@ def __init__( time_and_place_module: optionally, specify a module containing settings that create a sense of setting in a specific time and place. If not specified, a random module will be chosen from the default options. + seed: the random seed to use. """ # No need for supprting agents in this environment. del supporting_agent_module @@ -701,7 +707,9 @@ def __init__( _, sampled_settings = helper_functions.load_time_and_place_module( time_and_place_module=time_and_place_module, default_time_and_place_modules=DEFAULT_TIME_AND_PLACE_MODULES, + seed=seed, ) + self._rng = random.Random(sampled_settings.seed) contestant_names = list(sampled_settings.contestants.keys()) start_time = datetime.datetime( @@ -723,7 +731,7 @@ def __init__( clock_now=self._clock.now, ) shared_memories, shared_context, show_title = ( - get_shared_memories_and_context(model, contestant_names) + get_shared_memories_and_context(model, contestant_names, rng=self._rng) ) self._formative_memory_factory = formative_memories.FormativeMemoryFactory( model=self._model, @@ -735,8 +743,9 @@ def __init__( model=model, show_title=show_title, sampled_settings=sampled_settings, + rng=self._rng, ) - random.shuffle(main_player_configs) + self._rng.shuffle(main_player_configs) tasks = { config.name: functools.partial( diff --git a/examples/modular/environment/utils/helper_functions.py b/examples/modular/environment/utils/helper_functions.py index 43ab7b41..c8df52aa 100644 --- a/examples/modular/environment/utils/helper_functions.py +++ b/examples/modular/environment/utils/helper_functions.py @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Helper functions for loading modules to support environment configuration. -""" +"""Helper functions for loading modules to support environment configuration.""" from collections.abc import Sequence import importlib @@ -27,6 +26,7 @@ def load_time_and_place_module( default_time_and_place_modules: Sequence[str], time_and_place_module: str | None = None, + seed: int | None = None, ) -> tuple[types.ModuleType, Any]: """Load a module that adapts the setting to be a particular time and place. @@ -34,6 +34,7 @@ def load_time_and_place_module( default_time_and_place_modules: A list of modules to choose from. time_and_place_module: The name of the specific module to load. If None, a random module from default_time_and_place_modules will be chosen. + seed: The random seed to use for sampling the parameters. Returns: time_and_place_params: a module containing the settings for the time and @@ -45,11 +46,13 @@ def load_time_and_place_module( like the names of individual characters, which must be resampled for each run of the simulation. """ + seed = seed or random.getrandbits(63) + rng = random.Random(seed) if time_and_place_module is None: - time_and_place_module = random.choice(default_time_and_place_modules) + time_and_place_module = rng.choice(default_time_and_place_modules) # Load the environment config with importlib time_and_place_params = importlib.import_module( f'{modules.__name__}.{time_and_place_module}' ) - sampled_settings = time_and_place_params.sample_parameters() + sampled_settings = time_and_place_params.sample_parameters(seed=seed) return time_and_place_params, sampled_settings diff --git a/examples/modular/scenario/scenarios.py b/examples/modular/scenario/scenarios.py index 769e34bf..5fd503f4 100644 --- a/examples/modular/scenario/scenarios.py +++ b/examples/modular/scenario/scenarios.py @@ -316,6 +316,7 @@ def build_simulation( agent_base_module: str = DEFAULT_IMPORT_AGENT_BASE_MODULE, support_agent_base_module: str = DEFAULT_IMPORT_SUPPORT_AGENT_MODULE, env_base_module: str = DEFAULT_IMPORT_ENV_BASE_MODULE, + seed: int | None = None, ) -> RunnableSimulationWithMemories: """Builds a simulation from a scenario configuration.""" substrate_config = scenario_config.substrate_config @@ -358,5 +359,6 @@ def build_simulation( resident_visitor_modules=(resident_agent_module, visitor_agent_module), supporting_agent_module=supporting_agent_module, time_and_place_module=scenario_config.time_and_place_module, + seed=seed, ) return runnable_simulation