| |
| import os |
| import sys |
| import time |
| import re |
| import numpy as np |
| import pandas as pd |
| import matplotlib.pyplot as plt |
| import torch |
| from datetime import datetime |
|
|
| sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) |
|
|
| from solar_sys_environment import SolarSys |
| from PG.trainer.pg import PGAgent |
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
| def compute_jains_fairness(values: np.ndarray) -> float: |
| if len(values) == 0: |
| return 0.0 |
| if np.all(values == 0): |
| return 1.0 |
| num = (values.sum())**2 |
| den = len(values) * (values**2).sum() |
| return num / den |
|
|
| def main(): |
| |
| MODEL_PATH = "/path/to/project/pg_pennsylvania_10agents_10000eps/logs" |
| DATA_PATH = "/path/to/project/testing/10houses_30days_TEST.csv" |
| DAYS_TO_EVALUATE = 30 |
|
|
| model_path = MODEL_PATH |
| data_path = DATA_PATH |
| days_to_evaluate = DAYS_TO_EVALUATE |
| SOLAR_THRESHOLD = 0.5 |
|
|
| state_match = re.search(r"pg_(oklahoma|colorado|pennsylvania)_", model_path) |
| if not state_match: |
| raise ValueError( |
| "Could not automatically detect the state (oklahoma, colorado, or pennsylvania) " |
| "from the model path. Please ensure your model's parent folder is named correctly, " |
| "e.g., 'pg_oklahoma_...'" |
| ) |
| detected_state = state_match.group(1) |
| print(f"--- Detected state: {detected_state.upper()} ---") |
|
|
| |
| env = SolarSys( |
| data_path=data_path, |
| state=detected_state, |
| time_freq="15T" |
| ) |
| eval_steps = env.num_steps |
| house_ids = env.house_ids |
| num_agents = env.num_agents |
|
|
| |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| run_name = f"eval_pg_{num_agents}agents_{days_to_evaluate}days_{timestamp}" |
| output_folder = os.path.join("runs_with_battery", run_name) |
| logs_dir = os.path.join(output_folder, "logs") |
| plots_dir = os.path.join(output_folder, "plots") |
| for d in (logs_dir, plots_dir): |
| os.makedirs(d, exist_ok=True) |
| print(f"Saving evaluation outputs to: {output_folder}") |
| |
| local_dim = env.observation_space.shape[1] |
| act_dim = env.action_space.shape[1] |
| |
| |
| pg_agents = [] |
| for i in range(num_agents): |
| agent = PGAgent( |
| state_dim=local_dim, |
| action_dim=act_dim, |
| lr=2e-4, |
| gamma=0.95, |
| ) |
| |
| |
| agent_model_path = os.path.join(model_path, f"best_model_agent_{i}.pth") |
| if os.path.exists(agent_model_path): |
| agent.load(agent_model_path) |
| print(f"Loaded model for agent {i}") |
| else: |
| print(f"WARNING: Model file not found for agent {i}: {agent_model_path}") |
| |
| single_model_path = os.path.join(model_path, "best_model.pth") |
| if os.path.exists(single_model_path): |
| agent.load(single_model_path) |
| print(f"Loaded single model for agent {i}") |
| |
| agent.model.to(device).eval() |
| pg_agents.append(agent) |
|
|
| |
| all_logs = [] |
| daily_summaries = [] |
| step_timing_list = [] |
| |
| evaluation_start = time.time() |
|
|
| for day_idx in range(days_to_evaluate): |
| obs = env.reset() |
| done = False |
| step_count = 0 |
| day_logs = [] |
|
|
| while not done: |
| step_start_time = time.time() |
|
|
| |
| actions = [] |
| with torch.no_grad(): |
| for i in range(num_agents): |
| |
| state = torch.FloatTensor(obs[i]).unsqueeze(0).to(device) |
| |
| |
| mean, log_std, _ = pg_agents[i].model(state) |
|
|
| |
| action = mean.squeeze(0).cpu().numpy() |
| |
| |
| action = np.clip(action, 0.0, 1.0) |
| actions.append(action) |
| |
| actions = np.array(actions, dtype=np.float32) |
|
|
| next_obs, rewards, done, info = env.step(actions) |
| |
| |
| step_end_time = time.time() |
| step_duration = step_end_time - step_start_time |
| |
| print(f"[Day {day_idx+1}, Step {step_count}] Step time: {step_duration:.6f} seconds") |
|
|
| step_timing_list.append({ |
| "day": day_idx + 1, |
| "step": step_count, |
| "step_time_s": step_duration |
| }) |
|
|
| grid_price_now = env.get_grid_price(step_count) |
| peer_price_now = info.get("peer_price", env.get_peer_price(step_count, |
| float(info["p2p_sell"].sum()), |
| float(info["p2p_buy"].sum()))) |
|
|
| for i, hid in enumerate(house_ids): |
| is_battery_house = hid in env.batteries |
| p2p_buy = float(info["p2p_buy"][i]) |
| p2p_sell = float(info["p2p_sell"][i]) |
| charge_amount = float(info.get("charge_amount", [0]*num_agents)[i]) |
| discharge_amount = float(info.get("discharge_amount", [0]*num_agents)[i]) |
|
|
| day_logs.append({ |
| "day": day_idx + 1, |
| "step": step_count, |
| "house": hid, |
| "grid_import_no_p2p": float(info["grid_import_no_p2p"][i]), |
| "grid_import_with_p2p": float(info["grid_import_with_p2p"][i]), |
| "grid_export": float(info.get("grid_export", [0]*num_agents)[i]), |
| "p2p_buy": p2p_buy, |
| "p2p_sell": p2p_sell, |
| "actual_cost": float(info["costs"][i]), |
| "baseline_cost": float(info["grid_import_no_p2p"][i]) * grid_price_now, |
| "total_demand": float(env.demands[hid][step_count]), |
| "total_solar": float(env.solars[hid][step_count]), |
| "grid_price": grid_price_now, |
| "peer_price": peer_price_now, |
| "soc": (env.batteries[hid]["soc"] / env.batteries[hid]["max_capacity"]) if is_battery_house else np.nan, |
| "degradation_cost": ((charge_amount + discharge_amount) * env.batteries[hid]["degradation_cost_per_kwh"]) if is_battery_house else 0.0, |
| "reward": float(rewards[i]), |
| }) |
|
|
| obs = next_obs |
| step_count += 1 |
| if step_count >= eval_steps: |
| break |
| |
| day_df = pd.DataFrame(day_logs) |
| all_logs.extend(day_logs) |
|
|
| |
| grouped_house = day_df.groupby("house").sum(numeric_only=True) |
| grouped_step = day_df.groupby("step").sum(numeric_only=True) |
|
|
| total_demand = grouped_step["total_demand"].sum() |
| total_solar = grouped_step["total_solar"].sum() |
| total_p2p_buy = grouped_house["p2p_buy"].sum() |
| total_p2p_sell = grouped_house["p2p_sell"].sum() |
|
|
| baseline_cost_per_house = grouped_house["baseline_cost"] |
| actual_cost_per_house = grouped_house["actual_cost"] |
| cost_savings_per_house = baseline_cost_per_house - actual_cost_per_house |
| day_total_cost_savings = cost_savings_per_house.sum() |
| |
| if baseline_cost_per_house.sum() > 0: |
| overall_cost_savings_pct = day_total_cost_savings / baseline_cost_per_house.sum() |
| else: |
| overall_cost_savings_pct = 0.0 |
|
|
| baseline_import_per_house = grouped_house["grid_import_no_p2p"] |
| actual_import_per_house = grouped_house["grid_import_with_p2p"] |
| import_reduction_per_house = baseline_import_per_house - actual_import_per_house |
| day_total_import_reduction = import_reduction_per_house.sum() |
| |
| if baseline_import_per_house.sum() > 0: |
| overall_import_reduction_pct = day_total_import_reduction / baseline_import_per_house.sum() |
| else: |
| overall_import_reduction_pct = 0.0 |
|
|
| fairness_cost_savings = compute_jains_fairness(cost_savings_per_house.values) |
| fairness_import_reduction = compute_jains_fairness(import_reduction_per_house.values) |
| fairness_rewards = compute_jains_fairness(grouped_house["reward"].values) |
| fairness_p2p_buy = compute_jains_fairness(grouped_house["p2p_buy"].values) |
| fairness_p2p_sell = compute_jains_fairness(grouped_house["p2p_sell"].values) |
| fairness_p2p_total = compute_jains_fairness((grouped_house["p2p_buy"] + grouped_house["p2p_sell"]).values) |
| day_total_degradation_cost = grouped_house["degradation_cost"].sum() |
|
|
| daily_summaries.append({ |
| "day": day_idx + 1, |
| "day_total_demand": total_demand, |
| "day_total_solar": total_solar, |
| "day_p2p_buy": total_p2p_buy, |
| "day_p2p_sell": total_p2p_sell, |
| "cost_savings_abs": day_total_cost_savings, |
| "cost_savings_pct": overall_cost_savings_pct, |
| "fairness_cost_savings": fairness_cost_savings, |
| "grid_reduction_abs": day_total_import_reduction, |
| "grid_reduction_pct": overall_import_reduction_pct, |
| "fairness_grid_reduction": fairness_import_reduction, |
| "fairness_reward": fairness_rewards, |
| "fairness_p2p_buy": fairness_p2p_buy, |
| "fairness_p2p_sell": fairness_p2p_sell, |
| "fairness_p2p_total": fairness_p2p_total, |
| "total_degradation_cost": day_total_degradation_cost |
| }) |
|
|
| |
| evaluation_end = time.time() |
| total_eval_time = evaluation_end - evaluation_start |
| print(f"\nEvaluation loop finished. Total time: {total_eval_time:.2f} seconds.") |
| print(f"Device used: {device}") |
|
|
| all_days_df = pd.DataFrame(all_logs) |
| combined_csv_path = os.path.join(logs_dir, "step_logs_all_days.csv") |
| all_days_df.to_csv(combined_csv_path, index=False) |
| print(f"Saved combined step-level logs to: {combined_csv_path}") |
|
|
| step_timing_df = pd.DataFrame(step_timing_list) |
| timing_csv_path = os.path.join(logs_dir, "step_timing_log.csv") |
| step_timing_df.to_csv(timing_csv_path, index=False) |
| print(f"Saved step timing logs to: {timing_csv_path}") |
|
|
| house_level_df = all_days_df.groupby("house").agg({ |
| "baseline_cost": "sum", |
| "actual_cost": "sum", |
| "grid_import_no_p2p": "sum", |
| "grid_import_with_p2p": "sum", |
| "degradation_cost": "sum" |
| }) |
| house_level_df["cost_savings"] = house_level_df["baseline_cost"] - house_level_df["actual_cost"] |
| house_level_df["import_reduction"] = house_level_df["grid_import_no_p2p"] - house_level_df["grid_import_with_p2p"] |
| |
| house_summary_csv = os.path.join(logs_dir, "summary_per_house.csv") |
| house_level_df.to_csv(house_summary_csv) |
| print(f"Saved final summary per house to: {house_summary_csv}") |
|
|
| fairness_grid_all = compute_jains_fairness(house_level_df["import_reduction"].values) |
| fairness_cost_all = compute_jains_fairness(house_level_df["cost_savings"].values) |
| |
| daily_summary_df = pd.DataFrame(daily_summaries) |
|
|
| total_cost_savings_all = daily_summary_df["cost_savings_abs"].sum() |
| total_baseline_cost_all = all_days_df.groupby('day')['baseline_cost'].sum().sum() |
| pct_cost_savings_all = total_cost_savings_all / total_baseline_cost_all if total_baseline_cost_all > 0 else 0.0 |
|
|
| total_grid_reduction_all = daily_summary_df["grid_reduction_abs"].sum() |
| total_baseline_import_all = all_days_df.groupby('day')['grid_import_no_p2p'].sum().sum() |
| pct_grid_reduction_all = total_grid_reduction_all / total_baseline_import_all if total_baseline_import_all > 0 else 0.0 |
|
|
| total_degradation_cost_all = daily_summary_df["total_degradation_cost"].sum() |
|
|
| |
|
|
| |
| agg_solar_per_step = all_days_df.groupby(['day', 'step'])['total_solar'].sum() |
| sunny_steps_mask = agg_solar_per_step > (SOLAR_THRESHOLD * num_agents) |
| sunny_df = all_days_df.set_index(['day', 'step'])[sunny_steps_mask].reset_index() |
| baseline_import_sunny = sunny_df['grid_import_no_p2p'].sum() |
| actual_import_sunny = sunny_df['grid_import_with_p2p'].sum() |
| grid_reduction_sunny_pct = 0.0 |
| if baseline_import_sunny > 0: |
| grid_reduction_sunny_pct = (baseline_import_sunny - actual_import_sunny) / baseline_import_sunny |
|
|
| |
| total_p2p_buy = all_days_df['p2p_buy'].sum() |
| total_actual_grid_import = all_days_df['grid_import_with_p2p'].sum() |
| total_procured_energy = total_p2p_buy + total_actual_grid_import |
| community_sourcing_rate_pct = 0.0 |
| if total_procured_energy > 0: |
| community_sourcing_rate_pct = total_p2p_buy / total_procured_energy |
|
|
| |
| total_p2p_sell = all_days_df['p2p_sell'].sum() |
| total_grid_export = all_days_df['grid_export'].sum() |
| total_excess_solar = total_p2p_sell + total_grid_export |
| solar_sharing_efficiency_pct = 0.0 |
| if total_excess_solar > 0: |
| solar_sharing_efficiency_pct = total_p2p_sell / total_excess_solar |
|
|
| baseline_cost_sunny = sunny_df['baseline_cost'].sum() |
| actual_cost_sunny = sunny_df['actual_cost'].sum() |
| cost_savings_sunny_pct = (baseline_cost_sunny - actual_cost_sunny) / baseline_cost_sunny if baseline_cost_sunny > 0 else 0.0 |
|
|
| total_p2p_buy = all_days_df['p2p_buy'].sum() |
| total_actual_grid_import = all_days_df['grid_import_with_p2p'].sum() |
| community_sourcing_rate_pct = total_p2p_buy / (total_p2p_buy + total_actual_grid_import) if (total_p2p_buy + total_actual_grid_import) > 0 else 0.0 |
|
|
| total_p2p_sell = all_days_df['p2p_sell'].sum() |
| total_grid_export = all_days_df['grid_export'].sum() |
| solar_sharing_efficiency_pct = total_p2p_sell / (total_p2p_sell + total_grid_export) if (total_p2p_sell + total_grid_export) > 0 else 0.0 |
|
|
| final_row = { |
| "day": "ALL_DAYS_SUMMARY", |
| "cost_savings_abs": total_cost_savings_all, |
| "cost_savings_pct": pct_cost_savings_all, |
| "grid_reduction_abs": total_grid_reduction_all, |
| "grid_reduction_pct": pct_grid_reduction_all, |
| "fairness_cost_savings": fairness_cost_all, |
| "fairness_grid_reduction": fairness_grid_all, |
| "total_degradation_cost": total_degradation_cost_all, |
| "grid_reduction_sunny_hours_pct": grid_reduction_sunny_pct, |
| "community_sourcing_rate_pct": community_sourcing_rate_pct, |
| "solar_sharing_efficiency_pct": solar_sharing_efficiency_pct, |
| } |
| |
| for col in daily_summary_df.columns: |
| if col not in final_row: |
| final_row[col] = np.nan |
| final_row_df = pd.DataFrame([final_row]) |
|
|
| daily_summary_df = pd.concat([daily_summary_df, final_row_df], ignore_index=True) |
| summary_csv = os.path.join(logs_dir, "summary_per_day.csv") |
| daily_summary_df.to_csv(summary_csv, index=False) |
| print(f"Saved day-level summary with final multi-day row to: {summary_csv}") |
|
|
| |
| print("\n================== EVALUATION SUMMARY ==================") |
| print(f"Evaluation finished for {days_to_evaluate} days.\n") |
| |
| print("--- Standard Metrics (24-Hour Average) ---") |
| print(f"Total grid reduction: {total_grid_reduction_all:.2f} kWh ({pct_grid_reduction_all:.2%})") |
| print(f"Total cost savings: ${total_cost_savings_all:.2f} ({pct_cost_savings_all:.2%})") |
| print(f"Jain's fairness on grid reduction: {fairness_grid_all:.3f}") |
| print(f"Jain's fairness on cost savings: {fairness_cost_all:.3f}\n") |
|
|
| print("--- Alternative Metrics (Highlighting Peak Performance) ---") |
| print(f"Grid reduction during solar hours: {grid_reduction_sunny_pct:.2%}") |
| print(f"Cost savings during solar hours: {cost_savings_sunny_pct:.2%}") |
| print(f"Community sourcing rate: {community_sourcing_rate_pct:.2%}") |
| print(f"Solar sharing efficiency: {solar_sharing_efficiency_pct:.2%}") |
| |
| print("=========================================================") |
|
|
| |
| plot_daily_df = daily_summary_df[daily_summary_df["day"] != "ALL_DAYS_SUMMARY"].copy() |
| plot_daily_df["day"] = plot_daily_df["day"].astype(int) |
|
|
| |
| plt.figure(figsize=(12, 6)) |
| plt.bar(plot_daily_df["day"], plot_daily_df["cost_savings_pct"] * 100, color='skyblue') |
| plt.xlabel("Day") |
| plt.ylabel("Cost Savings (%)") |
| plt.title("Daily Community Cost Savings Percentage") |
| plt.xticks(plot_daily_df["day"]) |
| plt.grid(axis='y', linestyle='--', alpha=0.7) |
| plt.savefig(os.path.join(plots_dir, "daily_cost_savings_percentage.png")) |
| plt.close() |
|
|
| |
| plt.figure(figsize=(12, 6)) |
| bar_width = 0.4 |
| days = plot_daily_df["day"] |
| plt.bar(days - bar_width/2, plot_daily_df["day_total_demand"], width=bar_width, label="Total Demand", color='coral') |
| plt.bar(days + bar_width/2, plot_daily_df["day_total_solar"], width=bar_width, label="Total Solar Generation", color='gold') |
| plt.xlabel("Day") |
| plt.ylabel("Energy (kWh)") |
| plt.title("Total Community Demand vs. Solar Generation Per Day") |
| plt.xticks(days) |
| plt.legend() |
| plt.grid(axis='y', linestyle='--', alpha=0.7) |
| plt.savefig(os.path.join(plots_dir, "daily_demand_vs_solar.png")) |
| plt.close() |
|
|
| |
| step_group = all_days_df.groupby(["day", "step"]).sum(numeric_only=True).reset_index() |
| step_group["global_step"] = (step_group["day"] - 1) * env.num_steps + step_group["step"] |
| |
| fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 12), sharex=True) |
| |
| |
| ax1.plot(step_group["global_step"], step_group["grid_import_with_p2p"], label="Grid Import (with P2P)", color='r') |
| ax1.plot(step_group["global_step"], step_group["p2p_buy"], label="P2P Buy", color='g') |
| ax1.set_ylabel("Energy (kWh)") |
| ax1.set_title("Community Energy Consumption: Grid Import vs. P2P Buy") |
| ax1.legend() |
| ax1.grid(True, linestyle='--', alpha=0.6) |
|
|
| |
| ax2.plot(step_group["global_step"], step_group["grid_export"], label="Grid Export", color='orange') |
| ax2.plot(step_group["global_step"], step_group["p2p_sell"], label="P2P Sell", color='b') |
| ax2.set_xlabel("Global Timestep") |
| ax2.set_ylabel("Energy (kWh)") |
| ax2.set_title("Community Energy Generation: Grid Export vs. P2P Sell") |
| ax2.legend() |
| ax2.grid(True, linestyle='--', alpha=0.6) |
| |
| plt.tight_layout() |
| plt.savefig(os.path.join(plots_dir, "combined_energy_flows_timeseries.png")) |
| plt.close() |
|
|
| |
| daily_agg = all_days_df.groupby("day").sum(numeric_only=True) |
| |
| plt.figure(figsize=(12, 7)) |
| plt.bar(daily_agg.index, daily_agg["grid_import_with_p2p"], label="Grid Import (with P2P)", color='crimson') |
| plt.bar(daily_agg.index, daily_agg["p2p_buy"], bottom=daily_agg["grid_import_with_p2p"], label="P2P Buy", color='limegreen') |
| plt.plot(daily_agg.index, daily_agg["grid_import_no_p2p"], label="Baseline Grid Import (No P2P)", color='blue', linestyle='--', marker='o') |
| |
| plt.xlabel("Day") |
| plt.ylabel("Energy (kWh)") |
| plt.title("Daily Energy Procurement: Baseline vs. P2P+Grid") |
| plt.xticks(daily_agg.index) |
| plt.legend() |
| plt.grid(axis='y', linestyle='--', alpha=0.7) |
| plt.savefig(os.path.join(plots_dir, "daily_energy_procurement_stacked.png")) |
| plt.close() |
|
|
| |
| plt.figure(figsize=(12, 6)) |
| plt.plot(plot_daily_df["day"], plot_daily_df["fairness_cost_savings"], label="Cost Savings Fairness", marker='o') |
| plt.plot(plot_daily_df["day"], plot_daily_df["fairness_grid_reduction"], label="Grid Reduction Fairness", marker='s') |
| plt.plot(plot_daily_df["day"], plot_daily_df["fairness_reward"], label="Reward Fairness", marker='^') |
| plt.xlabel("Day") |
| plt.ylabel("Jain's Fairness Index") |
| plt.title("Daily Fairness Metrics") |
| plt.xticks(plot_daily_df["day"]) |
| plt.ylim(0, 1.05) |
| plt.legend() |
| plt.grid(True, linestyle='--', alpha=0.7) |
| plt.savefig(os.path.join(plots_dir, "daily_fairness_metrics.png")) |
| plt.close() |
|
|
| |
| fig, ax1 = plt.subplots(figsize=(15, 7)) |
| |
| house_ids_str = house_level_df.index.astype(str) |
| bar_width = 0.4 |
| index = np.arange(len(house_ids_str)) |
|
|
| color1 = 'tab:green' |
| ax1.set_xlabel('House ID') |
| ax1.set_ylabel('Total Cost Savings ($)', color=color1) |
| ax1.bar(index - bar_width/2, house_level_df["cost_savings"], bar_width, label='Cost Savings', color=color1) |
| ax1.tick_params(axis='y', labelcolor=color1) |
| ax1.set_xticks(index) |
| ax1.set_xticklabels(house_ids_str, rotation=45, ha="right") |
| |
| ax2 = ax1.twinx() |
| color2 = 'tab:blue' |
| ax2.set_ylabel('Total Grid Import Reduction (kWh)', color=color2) |
| ax2.bar(index + bar_width/2, house_level_df["import_reduction"], bar_width, label='Import Reduction', color=color2) |
| ax2.tick_params(axis='y', labelcolor=color2) |
|
|
| plt.title(f'Total Cost Savings & Grid Import Reduction Per House (over {days_to_evaluate} days)') |
| fig.tight_layout() |
| plt.savefig(os.path.join(plots_dir, "per_house_summary.png")) |
| plt.close() |
| |
| |
| day1_prices = all_days_df[all_days_df['day'] == 1][['step', 'grid_price', 'peer_price']].drop_duplicates() |
| plt.figure(figsize=(12, 6)) |
| plt.plot(day1_prices['step'], day1_prices['grid_price'], label='Grid Price', color='darkorange') |
| plt.plot(day1_prices['step'], day1_prices['peer_price'], label='P2P Price', color='teal') |
| plt.xlabel("Timestep of Day") |
| plt.ylabel("Price ($/kWh)") |
| plt.title("Price Dynamics on Day 1") |
| plt.legend() |
| plt.grid(True, linestyle='--', alpha=0.6) |
| plt.savefig(os.path.join(plots_dir, "price_dynamics_day1.png")) |
| plt.close() |
| |
| |
| day1_df = all_days_df[all_days_df['day'] == 1] |
| battery_houses = day1_df.dropna(subset=['soc'])['house'].unique() |
| |
| if len(battery_houses) > 0: |
| sample_houses = battery_houses[:min(4, len(battery_houses))] |
| plt.figure(figsize=(12, 6)) |
| for house in sample_houses: |
| house_df = day1_df[day1_df['house'] == house] |
| plt.plot(house_df['step'], house_df['soc'] * 100, label=f'House {house}') |
| |
| plt.xlabel("Timestep of Day") |
| plt.ylabel("State of Charge (%)") |
| plt.title("Battery SoC on Day 1 for Sample Houses") |
| plt.legend() |
| plt.grid(True, linestyle='--', alpha=0.6) |
| plt.savefig(os.path.join(plots_dir, "soc_dynamics_day1.png")) |
| plt.close() |
|
|
| print("All plots have been generated and saved. Evaluation complete.") |
|
|
| if __name__ == "__main__": |
| main() |