55
66from sorrel .utils .logging import ConsoleLogger , TensorboardLogger
77
8+ # Lowercased entity class names for A–E (see entities.py); always logged, including 0.
9+ _STATE_PUNISHMENT_RESOURCE_ENCOUNTER_TYPES = ("a" , "b" , "c" , "d" , "e" )
10+ _STATE_PUNISHMENT_RESOURCE_ENCOUNTER_SET = frozenset (_STATE_PUNISHMENT_RESOURCE_ENCOUNTER_TYPES )
11+
812
913class StatePunishmentLogger :
1014 """Enhanced logger that tracks encounters and punishment levels."""
@@ -33,6 +37,8 @@ def record_turn(
3337 encounter_data = {}
3438
3539 if self .multi_agent_env is not None :
40+ from sorrel .examples .state_punishment .agents import SeparateModelStatePunishmentAgent
41+
3642 # Initialize total and mean counters
3743 total_encounters = {}
3844 mean_encounters = {}
@@ -44,26 +50,39 @@ def record_turn(
4450 sigma_weights_advantage = []
4551 sigma_weights_value = []
4652
47- # Track action frequencies
48- total_action_frequencies = {}
49- mean_action_frequencies = {}
53+ n_envs = len (self .multi_agent_env .individual_envs )
54+ # Union of action keys so every epoch logs zeros for unused actions (and vote_* for separate-model agents)
55+ global_action_keys : set [str ] = set ()
56+ for env in self .multi_agent_env .individual_envs :
57+ ag = env .agents [0 ]
58+ global_action_keys .update (ag .action_names )
59+ if isinstance (ag , SeparateModelStatePunishmentAgent ):
60+ global_action_keys .update (
61+ ("vote_no" , "vote_increase" , "vote_decrease" )
62+ )
63+ sorted_action_keys = sorted (global_action_keys )
5064
5165 for i , env in enumerate (self .multi_agent_env .individual_envs ):
5266 agent = env .agents [0 ]
53- agent_count = len (agent .encounters )
5467
55- # Individual agent encounter data
68+ # Resource encounters (A–E): always log every step, including zeros
69+ for res in _STATE_PUNISHMENT_RESOURCE_ENCOUNTER_TYPES :
70+ count = agent .encounters .get (res , 0 )
71+ encounter_data [f"Agent_{ i } /{ res } _encounters" ] = count
72+ total_encounters [res ] = total_encounters .get (res , 0 ) + count
73+ mean_encounters [res ] = mean_encounters .get (res , 0 ) + count
74+
75+ # Other stepped-on entity types (e.g. sand, wall)
5676 for entity_type , count in agent .encounters .items ():
77+ if entity_type in _STATE_PUNISHMENT_RESOURCE_ENCOUNTER_SET :
78+ continue
5779 encounter_data [f"Agent_{ i } /{ entity_type } _encounters" ] = count
58-
59- # Initialize if first time seeing this entity type
6080 if entity_type not in total_encounters :
6181 total_encounters [entity_type ] = 0
6282 mean_encounters [entity_type ] = 0
63-
6483 total_encounters [entity_type ] += count
6584 mean_encounters [entity_type ] += count
66-
85+
6786 # Individual agent score
6887 encounter_data [f"Agent_{ i } /individual_score" ] = agent .individual_score
6988 total_individual_scores += agent .individual_score
@@ -72,21 +91,13 @@ def record_turn(
7291 encounter_data [f"Agent_{ i } /social_harm_received" ] = agent .social_harm_received_epoch
7392 total_social_harm_received += agent .social_harm_received_epoch
7493
75- # Track action frequencies for this agent
76- for action_name , frequency in agent .action_frequencies .items ():
94+ # Action frequencies: always emit every key in global union (zeros when unused)
95+ for action_name in sorted_action_keys :
96+ frequency = agent .action_frequencies .get (action_name , 0 )
7797 encounter_data [f"Agent_{ i } /action_freq_{ action_name } " ] = frequency
78-
79- # Initialize if first time seeing this action
80- if action_name not in total_action_frequencies :
81- total_action_frequencies [action_name ] = 0
82- mean_action_frequencies [action_name ] = 0
83-
84- total_action_frequencies [action_name ] += frequency
85- mean_action_frequencies [action_name ] += frequency
8698
8799 # Access sigma_weight and epsilon from PyTorchIQN model
88100 # Check if agent uses separate models
89- from sorrel .examples .state_punishment .agents import SeparateModelStatePunishmentAgent
90101 if isinstance (agent , SeparateModelStatePunishmentAgent ):
91102 # Separate model agent: log sigma weights and epsilon from both move and vote models
92103 # Move model sigma weights
@@ -147,26 +158,32 @@ def record_turn(
147158 # Add totals and means to encounter_data
148159 for entity_type in total_encounters :
149160 encounter_data [f"Total/total_{ entity_type } _encounters" ] = total_encounters [entity_type ]
150- encounter_data [f"Mean/mean_{ entity_type } _encounters" ] = mean_encounters [entity_type ] / len ( self . multi_agent_env . individual_envs )
161+ encounter_data [f"Mean/mean_{ entity_type } _encounters" ] = mean_encounters [entity_type ] / n_envs
151162
152163 # Add total and mean individual scores
153164 encounter_data ["Total/total_individual_score" ] = total_individual_scores
154- encounter_data ["Mean/mean_individual_score" ] = total_individual_scores / len ( self . multi_agent_env . individual_envs )
165+ encounter_data ["Mean/mean_individual_score" ] = total_individual_scores / n_envs
155166
156167 # Add total and mean social harm received
157168 encounter_data ["Total/total_social_harm_received" ] = total_social_harm_received
158- encounter_data ["Mean/mean_social_harm_received" ] = total_social_harm_received / len ( self . multi_agent_env . individual_envs )
169+ encounter_data ["Mean/mean_social_harm_received" ] = total_social_harm_received / n_envs
159170
160- # Add total and mean action frequencies
171+ # Add total and mean action frequencies (zeros included via .get above)
161172 # Note: For standard agents, each agent takes one action per turn, so the sum of mean
162173 # action frequencies should equal max_turns (typically 100) if the epoch completes.
163174 # For separate model agents, the total includes both movement actions (one per turn)
164175 # and vote actions (one per vote epoch), so the sum will be higher than max_turns.
165176 # For example: if max_turns=100 and vote_window_size=10, expect ~110 actions per agent
166177 # (100 movement + 10 vote actions). Epochs can end early if world.is_done is True.
167- for action_name in total_action_frequencies :
168- encounter_data [f"Total/total_action_freq_{ action_name } " ] = total_action_frequencies [action_name ]
169- encounter_data [f"Mean/mean_action_freq_{ action_name } " ] = mean_action_frequencies [action_name ] / len (self .multi_agent_env .individual_envs )
178+ for action_name in sorted_action_keys :
179+ total_af = sum (
180+ self .multi_agent_env .individual_envs [j ].agents [0 ].action_frequencies .get (
181+ action_name , 0
182+ )
183+ for j in range (n_envs )
184+ )
185+ encounter_data [f"Total/total_action_freq_{ action_name } " ] = total_af
186+ encounter_data [f"Mean/mean_action_freq_{ action_name } " ] = total_af / n_envs
170187
171188
172189 # Add mean sigma weights across all agents
0 commit comments