diff --git a/TASK_DETAILS.md b/TASK_DETAILS.md index b331f7d8..5ecf73d8 100644 --- a/TASK_DETAILS.md +++ b/TASK_DETAILS.md @@ -82,7 +82,11 @@ We welcome new engineering problem ideas — even without complete verification C++ SHA3-256 throughput (OpenSSL verified) - CommunicationEngineering + CommunicationEngineering + AdaptiveLinkScheduling + Downlink resource-block scheduling with MCS and power control under queue, latency, fairness, and power-budget constraints + + LDPCErrorFloor LDPC code error floor estimation via importance sampling on trapping sets @@ -298,7 +302,11 @@ We welcome new engineering problem ideas — even without complete verification pyMOTO-based 2D beam topology optimization (SIMP + OC/MMA) under a volume-fraction constraint - Robotics + Robotics + AGVWarehouseRouting + Warehouse AGV pick-sequence optimization with aisle obstacles, congestion, and turn costs + + DynamicObstacleAvoidanceNavigation Navigate a differential-drive robot from start to goal in a dynamic environment @@ -337,10 +345,14 @@ We welcome new engineering problem ideas — even without complete verification Importance-sampling BER estimator for Hamming(127,120) - PowerSystems + PowerSystems EV2GymSmartCharging Upstream-aligned EV smart charging scheduling + + MicrogridBatteryDispatch + Commercial microgrid battery dispatch under solar forecasts, tariffs, demand charges, and degradation cost + AdditiveManufacturing DiffSimThermalControl diff --git a/TASK_DETAILS_zh-CN.md b/TASK_DETAILS_zh-CN.md index 44d1b1fa..f6fdbd99 100644 --- a/TASK_DETAILS_zh-CN.md +++ b/TASK_DETAILS_zh-CN.md @@ -82,7 +82,11 @@ Frontier-Eng 目前已覆盖以下领域的任务。每个任务均配有可运 C++ SHA3-256 吞吐量(OpenSSL 验证) - CommunicationEngineering + CommunicationEngineering + AdaptiveLinkScheduling + 在队列、时延、公平性与功率预算约束下进行下行资源块调度、MCS 与功率控制 + + LDPCErrorFloor 使用 importance sampling 针对 trapping sets 估计 LDPC 码 error floor @@ -298,7 +302,11 @@ Frontier-Eng 目前已覆盖以下领域的任务。每个任务均配有可运 基于 pyMOTO 的 2D 梁拓扑优化(SIMP + OC/MMA),体积分数约束 - Robotics + Robotics + AGVWarehouseRouting + 考虑巷道障碍、拥堵与转向成本的仓储 AGV 拣货顺序优化 + + DynamicObstacleAvoidanceNavigation 在动态环境中控制差分轮机器人从起点到终点 @@ -337,10 +345,14 @@ Frontier-Eng 目前已覆盖以下领域的任务。每个任务均配有可运 使用 importance sampling 估计 Hamming(127,120) 的 BER - PowerSystems + PowerSystems EV2GymSmartCharging 上游对齐的电动车智能充电调度 + + MicrogridBatteryDispatch + 在太阳能预测、分时电价、需量电费与退化成本下优化商业微电网电池调度 + AdditiveManufacturing DiffSimThermalControl diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/README.md b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/README.md new file mode 100644 index 00000000..8f4cd933 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/README.md @@ -0,0 +1,47 @@ +# AdaptiveLinkScheduling + +Optimize per-resource-block user scheduling, modulation/coding selection, and +transmit power for a small wireless downlink under queue, channel, latency, and +power-budget constraints. + +## Files + +- `Task.md`: task contract and scoring details. +- `scripts/init.py`: editable seed scheduler. +- `verification/evaluator.py`: deterministic link simulator and scorer. +- `verification/requirements.txt`: evaluator dependencies. +- `frontier_eval/`: unified-task metadata. + +## Candidate Interface + +Edit `scripts/init.py` only. The evaluator imports: + +```python +schedule_frame(frame: dict) -> list[dict] +``` + +Return one decision per resource block. Each decision should contain: + +```python +{"user": , "mcs": , "power_dbm": } +``` + +## Quick Run + +From this directory: + +```bash +python verification/evaluator.py scripts/init.py +``` + +From the repository root: + +```bash +python -m frontier_eval \ + task=unified \ + task.benchmark=CommunicationEngineering/AdaptiveLinkScheduling \ + algorithm=openevolve \ + algorithm.iterations=0 +``` + +No GPU, Docker, API key, or external assets are required. diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/Task.md b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/Task.md new file mode 100644 index 00000000..40af9b5f --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/Task.md @@ -0,0 +1,57 @@ +# Adaptive Link Scheduling + +## Background + +Wireless base stations continually decide which users receive resource blocks, +what modulation and coding scheme (MCS) to use, and how much power to transmit. +Good schedulers exploit favorable channel states while protecting users with +urgent queues and respecting power budgets. These decisions directly affect +throughput, latency, energy use, and fairness. + +## Objective + +For each fixed frame snapshot, produce one scheduling decision per resource +block. The verifier simulates packet delivery using deterministic SNR thresholds +and scores weighted throughput minus outage, power, budget, and fairness costs. + +## Candidate API + +The evaluator imports `schedule_frame(frame)` from `scripts/init.py`. + +`frame` contains: + +- `frame_id` +- `num_resource_blocks` +- `mcs_table`: list of `{mcs, snr_threshold_db, bits_per_rb}` +- `power_min_dbm`, `power_max_dbm`, `power_budget_mw` +- `users`: each user has `id`, `queue_bits`, `latency_weight`, + `min_service_bits`, and `snr_estimate_db` per resource block + +Return a list with `num_resource_blocks` entries. Each entry should be a mapping +with `user`, `mcs`, and `power_dbm`. + +## Constraints + +- Do not import external packages. +- Do not read or write files. +- Keep the public `schedule_frame(frame)` interface. +- Keep all editable logic inside the EVOLVE block. +- The scheduler must be deterministic for the same input frame. + +## Scoring + +For each resource block, the evaluator computes: + +```text +effective_snr = snr_estimate_db + (power_dbm - 20) +``` + +The selected MCS succeeds if `effective_snr` reaches the MCS threshold plus a +small implementation margin. Successful transmissions deliver `bits_per_rb` +subject to the selected user's remaining queue. Failed transmissions consume +power and incur outage penalty. + +The frame utility combines latency-weighted delivered bits, minimum-service +satisfaction, Jain fairness, power cost, and budget violations. + +`combined_score = mean(frame_utility across frames)`, so higher is better. diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/agent_files.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/agent_files.txt new file mode 100644 index 00000000..e60e972a --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/agent_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +scripts/init.py +verification/evaluator.py +frontier_eval/constraints.txt diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/artifact_files.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/artifact_files.txt new file mode 100644 index 00000000..76dc893a --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/artifact_files.txt @@ -0,0 +1,2 @@ +metrics.json +artifacts.json diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/candidate_destination.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/candidate_destination.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/candidate_destination.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/constraints.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/constraints.txt new file mode 100644 index 00000000..4c698f62 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/constraints.txt @@ -0,0 +1,6 @@ +AdaptiveLinkScheduling constraints: +1) Modify only scripts/init.py. +2) Keep schedule_frame(frame) as the public entrypoint. +3) Return one decision per resource block with user, mcs, and power_dbm fields. +4) Do not use external packages, file I/O, network access, or randomness. +5) Keep EVOLVE-BLOCK markers intact and place editable scheduler logic inside them. diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/copy_files.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/copy_files.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/copy_files.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_command.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_command.txt new file mode 100644 index 00000000..527d23b5 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_command.txt @@ -0,0 +1 @@ +{python} verification/evaluator.py {candidate} --metrics-out metrics.json --artifacts-out artifacts.json diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_cwd.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_cwd.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/eval_cwd.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/initial_program.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/initial_program.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/initial_program.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/readonly_files.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/readonly_files.txt new file mode 100644 index 00000000..c3f5eb23 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/frontier_eval/readonly_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +verification/evaluator.py +verification/requirements.txt +frontier_eval/ diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/scripts/init.py b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/scripts/init.py new file mode 100644 index 00000000..63952e46 --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/scripts/init.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import Any + + +def _choose_mcs(snr_db: float, mcs_table: list[dict[str, Any]], margin_db: float = 2.0) -> int: + selected = int(mcs_table[0]["mcs"]) + for item in mcs_table: + if snr_db >= float(item["snr_threshold_db"]) + margin_db: + selected = int(item["mcs"]) + return selected + + +def schedule_frame(frame: dict[str, Any]) -> list[dict[str, float | int]]: + """Return one scheduling decision per resource block.""" + + # EVOLVE-BLOCK-START + users = frame["users"] + mcs_table = frame["mcs_table"] + num_rbs = int(frame["num_resource_blocks"]) + power_min = float(frame["power_min_dbm"]) + power_max = float(frame["power_max_dbm"]) + nominal_power = min(power_max, max(power_min, 20.0)) + + remaining = {int(user["id"]): float(user["queue_bits"]) for user in users} + served = {int(user["id"]): 0.0 for user in users} + decisions: list[dict[str, float | int]] = [] + + for rb in range(num_rbs): + best_user = None + best_score = None + for user in users: + user_id = int(user["id"]) + if remaining[user_id] <= 0: + continue + snr = float(user["snr_estimate_db"][rb]) + latency = float(user["latency_weight"]) + deficit = max(0.0, float(user["min_service_bits"]) - served[user_id]) + score = snr + 3.0 * latency + 0.002 * deficit + 0.0005 * remaining[user_id] + if best_score is None or score > best_score: + best_score = score + best_user = user + + if best_user is None: + decisions.append({"user": int(users[0]["id"]), "mcs": 0, "power_dbm": power_min}) + continue + + user_id = int(best_user["id"]) + snr_at_power = float(best_user["snr_estimate_db"][rb]) + (nominal_power - 20.0) + mcs = _choose_mcs(snr_at_power, mcs_table, margin_db=2.0) + bits = 0.0 + for item in mcs_table: + if int(item["mcs"]) == int(mcs): + bits = float(item["bits_per_rb"]) + break + delivered_estimate = min(bits, remaining[user_id]) + remaining[user_id] -= delivered_estimate + served[user_id] += delivered_estimate + decisions.append({"user": user_id, "mcs": int(mcs), "power_dbm": nominal_power}) + + return decisions + # EVOLVE-BLOCK-END + + +if __name__ == "__main__": + demo = { + "num_resource_blocks": 2, + "power_min_dbm": 5.0, + "power_max_dbm": 24.0, + "mcs_table": [ + {"mcs": 0, "snr_threshold_db": -3.0, "bits_per_rb": 180}, + {"mcs": 1, "snr_threshold_db": 1.0, "bits_per_rb": 300}, + ], + "users": [ + {"id": 0, "queue_bits": 1000, "latency_weight": 1.0, "min_service_bits": 200, "snr_estimate_db": [2.0, 1.0]}, + {"id": 1, "queue_bits": 800, "latency_weight": 1.4, "min_service_bits": 200, "snr_estimate_db": [0.0, 4.0]}, + ], + } + print(schedule_frame(demo)) diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/evaluator.py b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/evaluator.py new file mode 100644 index 00000000..2a03d8cf --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/evaluator.py @@ -0,0 +1,268 @@ +from __future__ import annotations + +import argparse +import importlib.util +import json +import math +import statistics +import sys +import time +import traceback +from pathlib import Path +from types import ModuleType +from typing import Any + + +INVALID_COMBINED_SCORE = -1e18 +MCS_TABLE = [ + {"mcs": 0, "snr_threshold_db": -3.0, "bits_per_rb": 180.0}, + {"mcs": 1, "snr_threshold_db": 1.0, "bits_per_rb": 300.0}, + {"mcs": 2, "snr_threshold_db": 4.0, "bits_per_rb": 480.0}, + {"mcs": 3, "snr_threshold_db": 8.0, "bits_per_rb": 720.0}, + {"mcs": 4, "snr_threshold_db": 12.0, "bits_per_rb": 960.0}, + {"mcs": 5, "snr_threshold_db": 16.0, "bits_per_rb": 1200.0}, +] + + +def _write_json(path: str | None, payload: dict[str, Any]) -> None: + if not path: + return + target = Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8") + + +def _load_candidate(candidate_path: Path) -> ModuleType: + spec = importlib.util.spec_from_file_location("link_scheduler_candidate", candidate_path) + if spec is None or spec.loader is None: + raise ImportError(f"failed to load candidate module from {candidate_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _frames() -> list[dict[str, Any]]: + frames: list[dict[str, Any]] = [] + for frame_id in range(18): + num_rbs = 12 + users: list[dict[str, Any]] = [] + for user_id in range(5): + queue = 1100.0 + 260.0 * ((frame_id + 2 * user_id) % 5) + 180.0 * math.sin(0.6 * frame_id + user_id) + latency = 0.8 + 0.25 * ((2 * user_id + frame_id) % 4) + min_service = 360.0 + 120.0 * ((user_id + frame_id) % 3) + snrs: list[float] = [] + for rb in range(num_rbs): + slow = 7.5 + 5.0 * math.sin(0.37 * frame_id + 0.73 * user_id) + selective = 4.3 * math.cos(0.61 * rb + 0.41 * user_id + 0.19 * frame_id) + edge_loss = -2.0 if user_id == 4 and frame_id % 3 == 1 else 0.0 + snrs.append(slow + selective + edge_loss) + users.append( + { + "id": user_id, + "queue_bits": max(250.0, queue), + "latency_weight": latency, + "min_service_bits": min_service, + "snr_estimate_db": snrs, + } + ) + frames.append( + { + "frame_id": f"frame_{frame_id:02d}", + "num_resource_blocks": num_rbs, + "mcs_table": [dict(item) for item in MCS_TABLE], + "power_min_dbm": 5.0, + "power_max_dbm": 24.0, + "power_budget_mw": 1450.0, + "implementation_margin_db": 1.25, + "users": users, + } + ) + return frames + + +def _mcs_by_id() -> dict[int, dict[str, float]]: + return {int(item["mcs"]): dict(item) for item in MCS_TABLE} + + +def _dbm_to_mw(power_dbm: float) -> float: + return 10.0 ** (power_dbm / 10.0) + + +def _coerce_decision(raw: Any) -> tuple[int, int, float]: + if not isinstance(raw, dict): + raise ValueError("each schedule entry must be a dict") + user = int(raw["user"]) + mcs = int(raw["mcs"]) + power = float(raw["power_dbm"]) + if not math.isfinite(power): + raise ValueError("power_dbm must be finite") + return user, mcs, power + + +def _public_frame(frame: dict[str, Any]) -> dict[str, Any]: + return { + "frame_id": frame["frame_id"], + "num_resource_blocks": frame["num_resource_blocks"], + "mcs_table": [dict(item) for item in frame["mcs_table"]], + "power_min_dbm": frame["power_min_dbm"], + "power_max_dbm": frame["power_max_dbm"], + "power_budget_mw": frame["power_budget_mw"], + "users": [ + { + "id": user["id"], + "queue_bits": user["queue_bits"], + "latency_weight": user["latency_weight"], + "min_service_bits": user["min_service_bits"], + "snr_estimate_db": list(user["snr_estimate_db"]), + } + for user in frame["users"] + ], + } + + +def _jain(values: list[float]) -> float: + if not values: + return 0.0 + total = sum(values) + sq = sum(v * v for v in values) + if sq <= 0.0: + return 0.0 + return total * total / (len(values) * sq) + + +def _score_frame(schedule_frame: Any, frame: dict[str, Any]) -> dict[str, Any]: + public = _public_frame(frame) + raw_schedule = schedule_frame(public) + if not isinstance(raw_schedule, (list, tuple)): + raise ValueError("schedule_frame must return a list") + if len(raw_schedule) != int(frame["num_resource_blocks"]): + raise ValueError( + f"expected {frame['num_resource_blocks']} decisions, got {len(raw_schedule)}" + ) + + mcs_lookup = _mcs_by_id() + users = {int(user["id"]): dict(user) for user in frame["users"]} + remaining = {user_id: float(user["queue_bits"]) for user_id, user in users.items()} + delivered = {user_id: 0.0 for user_id in users} + weighted_kbits = 0.0 + power_mw_total = 0.0 + outage_count = 0 + invalid_penalty = 0.0 + + for rb, raw in enumerate(raw_schedule): + try: + user_id, mcs_id, power_dbm = _coerce_decision(raw) + except Exception: + invalid_penalty += 20.0 + continue + if user_id not in users or mcs_id not in mcs_lookup: + invalid_penalty += 20.0 + continue + + clipped_power = max(float(frame["power_min_dbm"]), min(float(frame["power_max_dbm"]), power_dbm)) + invalid_penalty += abs(power_dbm - clipped_power) * 0.5 + power_dbm = clipped_power + power_mw = _dbm_to_mw(power_dbm) + power_mw_total += power_mw + + user = users[user_id] + mcs = mcs_lookup[mcs_id] + effective_snr = float(user["snr_estimate_db"][rb]) + (power_dbm - 20.0) + threshold = float(mcs["snr_threshold_db"]) + float(frame["implementation_margin_db"]) + if effective_snr + 1e-9 < threshold: + outage_count += 1 + continue + + bits = min(float(mcs["bits_per_rb"]), remaining[user_id]) + if bits <= 0.0: + continue + remaining[user_id] -= bits + delivered[user_id] += bits + weighted_kbits += float(user["latency_weight"]) * bits / 1000.0 + + service_bonus = 0.0 + service_shortfall = 0.0 + for user_id, user in users.items(): + target = float(user["min_service_bits"]) + got = delivered[user_id] + service_bonus += min(got, target) / 1000.0 + service_shortfall += max(0.0, target - got) / 1000.0 + + fairness = _jain([delivered[user_id] for user_id in sorted(delivered)]) + budget_excess = max(0.0, power_mw_total - float(frame["power_budget_mw"])) + utility = ( + weighted_kbits + + 1.25 * service_bonus + + 3.5 * fairness + - 0.0018 * power_mw_total + - 0.010 * budget_excess + - 0.85 * outage_count + - 1.7 * service_shortfall + - invalid_penalty + ) + return { + "frame_id": frame["frame_id"], + "utility": utility, + "delivered_bits_total": sum(delivered.values()), + "power_mw_total": power_mw_total, + "outage_count": outage_count, + "fairness": fairness, + "service_shortfall_kbits": service_shortfall, + "invalid_penalty": invalid_penalty, + "delivered_by_user": delivered, + } + + +def evaluate(candidate_path: str) -> tuple[dict[str, float], dict[str, Any]]: + module = _load_candidate(Path(candidate_path).resolve()) + schedule_frame = getattr(module, "schedule_frame", None) + if schedule_frame is None: + raise AttributeError("candidate must define schedule_frame(frame)") + frame_results = [_score_frame(schedule_frame, frame) for frame in _frames()] + mean_utility = statistics.fmean(float(item["utility"]) for item in frame_results) + mean_bits = statistics.fmean(float(item["delivered_bits_total"]) for item in frame_results) + mean_outages = statistics.fmean(float(item["outage_count"]) for item in frame_results) + metrics = { + "combined_score": float(mean_utility), + "valid": 1.0, + "mean_frame_utility": float(mean_utility), + "mean_delivered_bits": float(mean_bits), + "mean_outage_count": float(mean_outages), + } + artifacts = { + "frame_results": frame_results, + "score_direction": "higher_is_better", + } + return metrics, artifacts + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("candidate") + parser.add_argument("--metrics-out", default="metrics.json") + parser.add_argument("--artifacts-out", default="artifacts.json") + args = parser.parse_args(argv) + + start = time.time() + try: + metrics, artifacts = evaluate(args.candidate) + except Exception as exc: + metrics = { + "combined_score": INVALID_COMBINED_SCORE, + "valid": 0.0, + "runtime_s": float(time.time() - start), + } + artifacts = { + "error_message": str(exc), + "traceback": traceback.format_exc(), + } + metrics["runtime_s"] = float(time.time() - start) + artifacts["candidate_path"] = str(Path(args.candidate).resolve()) + _write_json(args.metrics_out, metrics) + _write_json(args.artifacts_out, artifacts) + print(json.dumps(metrics, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/requirements.txt b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/requirements.txt new file mode 100644 index 00000000..821e76fa --- /dev/null +++ b/benchmarks/CommunicationEngineering/AdaptiveLinkScheduling/verification/requirements.txt @@ -0,0 +1 @@ +# Python standard library only. diff --git a/benchmarks/CommunicationEngineering/README.md b/benchmarks/CommunicationEngineering/README.md index 97c8d3ae..160adb45 100644 --- a/benchmarks/CommunicationEngineering/README.md +++ b/benchmarks/CommunicationEngineering/README.md @@ -19,6 +19,10 @@ Communication engineering tasks involve optimizing various aspects of communicat ## Subtask Index +- `AdaptiveLinkScheduling/`: Optimize downlink resource-block scheduling, MCS selection, and transmit power under queue, channel, latency, fairness, and power-budget constraints. + - `frontier_eval` task name: `task=unified task.benchmark=CommunicationEngineering/AdaptiveLinkScheduling` + - quick run: `python -m frontier_eval task=unified task.benchmark=CommunicationEngineering/AdaptiveLinkScheduling algorithm.iterations=0` + - `LDPCErrorFloor/`: Estimate error floor for LDPC codes using importance sampling to handle rare trapping set events. - `frontier_eval` task name: `ldpc_error_floor` - quick run: `python -m frontier_eval task=ldpc_error_floor algorithm.iterations=0` @@ -30,4 +34,3 @@ Communication engineering tasks involve optimizing various aspects of communicat - `PMDSimulation/`: Simulate Polarization Mode Dispersion (PMD) in optical fiber systems using importance sampling for rare outage events. - `frontier_eval` task name: `pmd_simulation` - quick run: `python -m frontier_eval task=pmd_simulation algorithm.iterations=0` - diff --git a/benchmarks/CommunicationEngineering/README_zh-CN.md b/benchmarks/CommunicationEngineering/README_zh-CN.md index 0b825bce..0cc2c7c3 100644 --- a/benchmarks/CommunicationEngineering/README_zh-CN.md +++ b/benchmarks/CommunicationEngineering/README_zh-CN.md @@ -19,6 +19,10 @@ ## 子任务索引 +- `AdaptiveLinkScheduling/`: 在队列、信道、时延、公平性与功率预算约束下优化下行资源块调度、MCS 选择和发射功率。 + - `frontier_eval` 任务: `task=unified task.benchmark=CommunicationEngineering/AdaptiveLinkScheduling` + - 快速运行: `python -m frontier_eval task=unified task.benchmark=CommunicationEngineering/AdaptiveLinkScheduling algorithm.iterations=0` + - `LDPCErrorFloor/`: 使用重要性采样估计LDPC码的错误地板,处理罕见的trapping set事件。 - `frontier_eval` 任务: `task=unified task.benchmark=CommunicationEngineering/LDPCErrorFloor` - 快速运行: `python -m frontier_eval task=unified task.benchmark=CommunicationEngineering/LDPCErrorFloor algorithm.iterations=0` @@ -30,4 +34,3 @@ - `PMDSimulation/`: 使用重要性采样仿真光纤系统中的极化模色散(PMD),处理罕见的停机事件。 - `frontier_eval` 任务: `task=unified task.benchmark=CommunicationEngineering/PMDSimulation` - 快速运行: `python -m frontier_eval task=unified task.benchmark=CommunicationEngineering/PMDSimulation algorithm.iterations=0` - diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/README.md b/benchmarks/PowerSystems/MicrogridBatteryDispatch/README.md new file mode 100644 index 00000000..be94c8ad --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/README.md @@ -0,0 +1,45 @@ +# MicrogridBatteryDispatch + +Optimize battery dispatch for a grid-connected commercial microgrid with solar PV, +time-varying tariffs, demand charges, round-trip losses, and degradation cost. + +## Files + +- `Task.md`: task contract, model, and scoring details. +- `scripts/init.py`: editable seed policy used by agents. +- `verification/evaluator.py`: deterministic simulator and scorer. +- `verification/requirements.txt`: evaluator dependencies. +- `frontier_eval/`: unified-task metadata. + +## Candidate Interface + +Edit `scripts/init.py` only. The evaluator imports: + +```python +dispatch_action(state: dict) -> float +``` + +The returned value is battery power in kW. Positive values discharge the battery +to serve load, and negative values charge the battery. The evaluator enforces +all physical limits and penalizes requests that exceed feasible charge or +discharge bounds. + +## Quick Run + +From this directory: + +```bash +python verification/evaluator.py scripts/init.py +``` + +From the repository root: + +```bash +python -m frontier_eval \ + task=unified \ + task.benchmark=PowerSystems/MicrogridBatteryDispatch \ + algorithm=openevolve \ + algorithm.iterations=0 +``` + +No GPU, Docker, API key, or external assets are required. diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/Task.md b/benchmarks/PowerSystems/MicrogridBatteryDispatch/Task.md new file mode 100644 index 00000000..6d47f2cf --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/Task.md @@ -0,0 +1,64 @@ +# Microgrid Battery Dispatch + +## Background + +Commercial microgrids with rooftop solar and battery storage can reduce +electricity cost by shifting energy away from expensive hours, absorbing local +solar surplus, and limiting monthly peak demand. A practical controller must +respect battery limits, round-trip losses, and degradation cost while reacting to +load, solar, and tariff forecasts. + +## Objective + +Write a deterministic battery dispatch policy that minimizes operating cost over +several fixed microgrid cases. Each case contains hourly load, PV generation, +buy/sell tariff, demand charge, battery capacity, charge/discharge limits, and +efficiency values. + +## Candidate API + +The evaluator imports `dispatch_action(state)` from `scripts/init.py`. + +`state` contains: + +- `case_id`, `t`, `hour` +- `soc_kwh`, `min_soc_kwh`, `capacity_kwh` +- `max_charge_kw`, `max_discharge_kw` +- `charge_efficiency`, `discharge_efficiency` +- `load_forecast_kw`, `pv_forecast_kw` +- `buy_price_forecast`, `sell_price_forecast` +- `demand_charge_per_kw` +- `degradation_cost_per_kwh` + +The function returns a single finite float: + +- positive: discharge kW +- negative: charge kW +- zero: idle + +The simulation timestep is one hour. + +## Constraints + +- Do not import external packages. +- Do not read or write files. +- Keep the public `dispatch_action(state)` interface. +- Keep all editable logic inside the EVOLVE block. +- The policy must be deterministic for the same input state. + +## Scoring + +For each case the evaluator simulates the dispatch policy and computes: + +```text +total_cost = + grid_import_cost + - export_credit + + demand_charge + + battery_degradation_cost + + infeasible_action_penalty +``` + +`combined_score = -mean(total_cost across cases)`, so higher is better. +Feasible policies receive `valid = 1.0`; non-finite actions or evaluator errors +receive an invalid score. diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/agent_files.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/agent_files.txt new file mode 100644 index 00000000..e60e972a --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/agent_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +scripts/init.py +verification/evaluator.py +frontier_eval/constraints.txt diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/artifact_files.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/artifact_files.txt new file mode 100644 index 00000000..76dc893a --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/artifact_files.txt @@ -0,0 +1,2 @@ +metrics.json +artifacts.json diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/candidate_destination.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/candidate_destination.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/candidate_destination.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/constraints.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/constraints.txt new file mode 100644 index 00000000..873c9d0e --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/constraints.txt @@ -0,0 +1,6 @@ +MicrogridBatteryDispatch constraints: +1) Modify only scripts/init.py. +2) Keep dispatch_action(state) as the public entrypoint. +3) Return one finite float: positive for discharge kW, negative for charge kW. +4) Do not use external packages, file I/O, network access, or randomness. +5) Keep EVOLVE-BLOCK markers intact and place editable policy logic inside them. diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/copy_files.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/copy_files.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/copy_files.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_command.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_command.txt new file mode 100644 index 00000000..527d23b5 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_command.txt @@ -0,0 +1 @@ +{python} verification/evaluator.py {candidate} --metrics-out metrics.json --artifacts-out artifacts.json diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_cwd.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_cwd.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/eval_cwd.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/initial_program.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/initial_program.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/initial_program.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/readonly_files.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/readonly_files.txt new file mode 100644 index 00000000..c3f5eb23 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/frontier_eval/readonly_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +verification/evaluator.py +verification/requirements.txt +frontier_eval/ diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/scripts/init.py b/benchmarks/PowerSystems/MicrogridBatteryDispatch/scripts/init.py new file mode 100644 index 00000000..67b723d6 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/scripts/init.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import Any + + +def dispatch_action(state: dict[str, Any]) -> float: + """Return battery power in kW: positive discharges, negative charges.""" + + # EVOLVE-BLOCK-START + soc = float(state["soc_kwh"]) + capacity = float(state["capacity_kwh"]) + min_soc = float(state["min_soc_kwh"]) + max_discharge = float(state["max_discharge_kw"]) + discharge_eff = float(state["discharge_efficiency"]) + + load_forecast = [float(x) for x in state["load_forecast_kw"][:24]] + pv_forecast = [float(x) for x in state["pv_forecast_kw"][:24]] + net_forecast = [load - pv for load, pv in zip(load_forecast, pv_forecast)] + net_load = net_forecast[0] if net_forecast else 0.0 + + discharge_room_kw = max(0.0, (soc - min_soc) * max(discharge_eff, 1e-9)) + reserve_soc = min(capacity, min_soc + 0.05 * (capacity - min_soc)) + available_discharge = min(discharge_room_kw, max(0.0, (soc - reserve_soc) * discharge_eff)) + future_peak = max(net_forecast) if net_forecast else net_load + peak_target = max(90.0, 0.90 * future_peak) + shave_kw = max(0.0, net_load - peak_target) + + if shave_kw > 0.0 and available_discharge > 0.0: + return min(max_discharge, shave_kw, available_discharge) + + return 0.0 + # EVOLVE-BLOCK-END + + +if __name__ == "__main__": + demo_state = { + "soc_kwh": 90.0, + "min_soc_kwh": 20.0, + "capacity_kwh": 160.0, + "max_charge_kw": 45.0, + "max_discharge_kw": 45.0, + "charge_efficiency": 0.94, + "discharge_efficiency": 0.93, + "load_forecast_kw": [120.0] * 24, + "pv_forecast_kw": [80.0] * 24, + "buy_price_forecast": [0.18] * 24, + "sell_price_forecast": [0.05] * 24, + } + print(dispatch_action(demo_state)) diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/evaluator.py b/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/evaluator.py new file mode 100644 index 00000000..6af5f1b1 --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/evaluator.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import argparse +import importlib.util +import json +import math +import statistics +import sys +import time +import traceback +from pathlib import Path +from types import ModuleType +from typing import Any + + +INVALID_COMBINED_SCORE = -1e18 + + +def _write_json(path: str | None, payload: dict[str, Any]) -> None: + if not path: + return + target = Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8") + + +def _load_candidate(candidate_path: Path) -> ModuleType: + spec = importlib.util.spec_from_file_location("microgrid_candidate", candidate_path) + if spec is None or spec.loader is None: + raise ImportError(f"failed to load candidate module from {candidate_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _case_profiles(case_id: str, hours: int, scale: float, pv_scale: float, phase: float) -> dict[str, Any]: + load: list[float] = [] + pv: list[float] = [] + buy: list[float] = [] + sell: list[float] = [] + for t in range(hours): + hour = t % 24 + weekday = (t // 24) % 7 + morning = math.exp(-((hour - 8.0) / 3.0) ** 2) + evening = math.exp(-((hour - 18.0) / 4.0) ** 2) + base = scale * (86.0 + 16.0 * morning + 34.0 * evening) + weather = 1.0 + 0.08 * math.sin(0.47 * t + phase) + 0.04 * math.cos(0.19 * t) + load.append(max(25.0, base * weather + (6.0 if weekday >= 5 else 0.0))) + + sun = max(0.0, math.sin(math.pi * (hour - 6.0) / 12.0)) + cloud = 0.72 + 0.20 * math.sin(0.31 * t + phase) + 0.08 * math.cos(0.11 * t + 1.7) + pv.append(max(0.0, pv_scale * 92.0 * sun * max(0.15, cloud))) + + peak = 1.0 if 16 <= hour <= 21 else 0.0 + shoulder = 1.0 if 7 <= hour <= 15 else 0.0 + night = 1.0 if hour <= 5 or hour >= 22 else 0.0 + buy_price = 0.105 + 0.175 * peak + 0.055 * shoulder - 0.018 * night + buy_price += 0.012 * math.sin(0.23 * t + phase) + buy.append(max(0.05, buy_price)) + sell.append(max(0.025, 0.42 * buy[-1] - 0.015)) + + return { + "case_id": case_id, + "load_kw": load, + "pv_kw": pv, + "buy_price": buy, + "sell_price": sell, + } + + +def _build_cases() -> list[dict[str, Any]]: + cases = [ + _case_profiles("office_summer_peak", 96, 1.05, 1.05, 0.2), + _case_profiles("hospital_cloudy_week", 120, 1.28, 0.62, 1.4), + _case_profiles("warehouse_solar_rich", 96, 0.82, 1.38, 2.6), + ] + params = [ + { + "capacity_kwh": 260.0, + "initial_soc_kwh": 132.0, + "min_soc_kwh": 31.0, + "max_charge_kw": 70.0, + "max_discharge_kw": 72.0, + "charge_efficiency": 0.94, + "discharge_efficiency": 0.93, + "demand_charge_per_kw": 15.5, + "degradation_cost_per_kwh": 0.018, + }, + { + "capacity_kwh": 420.0, + "initial_soc_kwh": 250.0, + "min_soc_kwh": 84.0, + "max_charge_kw": 95.0, + "max_discharge_kw": 92.0, + "charge_efficiency": 0.93, + "discharge_efficiency": 0.92, + "demand_charge_per_kw": 18.0, + "degradation_cost_per_kwh": 0.022, + }, + { + "capacity_kwh": 190.0, + "initial_soc_kwh": 78.0, + "min_soc_kwh": 23.0, + "max_charge_kw": 54.0, + "max_discharge_kw": 58.0, + "charge_efficiency": 0.95, + "discharge_efficiency": 0.94, + "demand_charge_per_kw": 12.5, + "degradation_cost_per_kwh": 0.016, + }, + ] + for case, extra in zip(cases, params, strict=True): + case.update(extra) + return cases + + +def _forecast(values: list[float], t: int, horizon: int = 24) -> list[float]: + if not values: + return [] + return [float(values[min(len(values) - 1, t + k)]) for k in range(horizon)] + + +def _simulate_case(dispatch_action: Any, case: dict[str, Any]) -> dict[str, Any]: + soc = float(case["initial_soc_kwh"]) + capacity = float(case["capacity_kwh"]) + min_soc = float(case["min_soc_kwh"]) + charge_eff = float(case["charge_efficiency"]) + discharge_eff = float(case["discharge_efficiency"]) + max_charge = float(case["max_charge_kw"]) + max_discharge = float(case["max_discharge_kw"]) + + import_cost = 0.0 + export_credit = 0.0 + degradation_cost = 0.0 + infeasible_penalty = 0.0 + peak_import_kw = 0.0 + throughput_kwh = 0.0 + actions: list[float] = [] + soc_trace: list[float] = [soc] + + hours = len(case["load_kw"]) + for t in range(hours): + state = { + "case_id": case["case_id"], + "t": t, + "hour": t % 24, + "soc_kwh": soc, + "min_soc_kwh": min_soc, + "capacity_kwh": capacity, + "max_charge_kw": max_charge, + "max_discharge_kw": max_discharge, + "charge_efficiency": charge_eff, + "discharge_efficiency": discharge_eff, + "demand_charge_per_kw": float(case["demand_charge_per_kw"]), + "degradation_cost_per_kwh": float(case["degradation_cost_per_kwh"]), + "load_forecast_kw": _forecast(case["load_kw"], t), + "pv_forecast_kw": _forecast(case["pv_kw"], t), + "buy_price_forecast": _forecast(case["buy_price"], t), + "sell_price_forecast": _forecast(case["sell_price"], t), + } + raw_action = dispatch_action(state) + try: + requested = float(raw_action) + except Exception as exc: + raise ValueError(f"dispatch_action returned non-numeric value at t={t}: {raw_action!r}") from exc + if not math.isfinite(requested): + raise ValueError(f"dispatch_action returned non-finite value at t={t}: {requested!r}") + + charge_limit = min(max_charge, max(0.0, (capacity - soc) / max(charge_eff, 1e-9))) + discharge_limit = min(max_discharge, max(0.0, (soc - min_soc) * max(discharge_eff, 1e-9))) + applied = max(-charge_limit, min(discharge_limit, requested)) + infeasible_penalty += abs(requested - applied) * 1.5 + + if applied >= 0.0: + soc -= applied / max(discharge_eff, 1e-9) + else: + soc += (-applied) * charge_eff + soc = min(capacity, max(min_soc, soc)) + + load = float(case["load_kw"][t]) + pv = float(case["pv_kw"][t]) + grid_kw = load - pv - applied + grid_import = max(0.0, grid_kw) + grid_export = max(0.0, -grid_kw) + import_cost += grid_import * float(case["buy_price"][t]) + export_credit += grid_export * float(case["sell_price"][t]) + throughput_kwh += abs(applied) + degradation_cost += abs(applied) * float(case["degradation_cost_per_kwh"]) + peak_import_kw = max(peak_import_kw, grid_import) + actions.append(applied) + soc_trace.append(soc) + + demand_charge = peak_import_kw * float(case["demand_charge_per_kw"]) + total_cost = import_cost - export_credit + degradation_cost + demand_charge + infeasible_penalty + return { + "case_id": case["case_id"], + "total_cost": total_cost, + "import_cost": import_cost, + "export_credit": export_credit, + "degradation_cost": degradation_cost, + "demand_charge": demand_charge, + "infeasible_penalty": infeasible_penalty, + "peak_import_kw": peak_import_kw, + "throughput_kwh": throughput_kwh, + "final_soc_kwh": soc, + "mean_action_kw": statistics.fmean(actions) if actions else 0.0, + "soc_min_kwh": min(soc_trace), + "soc_max_kwh": max(soc_trace), + } + + +def evaluate(candidate_path: str) -> tuple[dict[str, float], dict[str, Any]]: + module = _load_candidate(Path(candidate_path).resolve()) + dispatch_action = getattr(module, "dispatch_action", None) + if dispatch_action is None: + raise AttributeError("candidate must define dispatch_action(state)") + + case_results = [_simulate_case(dispatch_action, case) for case in _build_cases()] + mean_cost = statistics.fmean(float(item["total_cost"]) for item in case_results) + mean_peak = statistics.fmean(float(item["peak_import_kw"]) for item in case_results) + mean_penalty = statistics.fmean(float(item["infeasible_penalty"]) for item in case_results) + metrics = { + "combined_score": -float(mean_cost), + "valid": 1.0, + "mean_total_cost": float(mean_cost), + "mean_peak_import_kw": float(mean_peak), + "mean_infeasible_penalty": float(mean_penalty), + } + artifacts = { + "case_results": case_results, + "score_direction": "higher_is_better", + } + return metrics, artifacts + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("candidate") + parser.add_argument("--metrics-out", default="metrics.json") + parser.add_argument("--artifacts-out", default="artifacts.json") + args = parser.parse_args(argv) + + start = time.time() + try: + metrics, artifacts = evaluate(args.candidate) + except Exception as exc: + metrics = { + "combined_score": INVALID_COMBINED_SCORE, + "valid": 0.0, + "runtime_s": float(time.time() - start), + } + artifacts = { + "error_message": str(exc), + "traceback": traceback.format_exc(), + } + metrics["runtime_s"] = float(time.time() - start) + artifacts["candidate_path"] = str(Path(args.candidate).resolve()) + _write_json(args.metrics_out, metrics) + _write_json(args.artifacts_out, artifacts) + print(json.dumps(metrics, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/requirements.txt b/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/requirements.txt new file mode 100644 index 00000000..821e76fa --- /dev/null +++ b/benchmarks/PowerSystems/MicrogridBatteryDispatch/verification/requirements.txt @@ -0,0 +1 @@ +# Python standard library only. diff --git a/benchmarks/PowerSystems/README.md b/benchmarks/PowerSystems/README.md index 6a3f9d2e..e0e2ca90 100644 --- a/benchmarks/PowerSystems/README.md +++ b/benchmarks/PowerSystems/README.md @@ -9,3 +9,8 @@ Current tasks emphasize realistic operational constraints, economic objectives, - Unified benchmark: `task=unified task.benchmark=PowerSystems/EV2GymSmartCharging` - Quick run: `python -m frontier_eval task=unified task.benchmark=PowerSystems/EV2GymSmartCharging task.runtime.env_name=frontier-eval-driver algorithm.iterations=0` - Description: upstream-aligned EV smart charging with transformer constraints in the real `EV2Gym` simulator + +- `MicrogridBatteryDispatch` + - Unified benchmark: `task=unified task.benchmark=PowerSystems/MicrogridBatteryDispatch` + - Quick run: `python -m frontier_eval task=unified task.benchmark=PowerSystems/MicrogridBatteryDispatch algorithm.iterations=0` + - Description: battery dispatch for a solar commercial microgrid under tariffs, demand charges, degradation, and physical limits diff --git a/benchmarks/PowerSystems/README_zh-CN.md b/benchmarks/PowerSystems/README_zh-CN.md index 30fcfd26..db65486e 100644 --- a/benchmarks/PowerSystems/README_zh-CN.md +++ b/benchmarks/PowerSystems/README_zh-CN.md @@ -9,3 +9,8 @@ - `frontier_eval` 任务:`task=unified task.benchmark=PowerSystems/EV2GymSmartCharging` - 快速运行:`python -m frontier_eval task=unified task.benchmark=PowerSystems/EV2GymSmartCharging task.runtime.env_name=frontier-eval-driver algorithm.iterations=0` - 简介:在真实上游 `EV2Gym` 模拟器中进行、与上游数据对齐的 EV 智能充电与变压器约束优化 + +- `MicrogridBatteryDispatch` + - `frontier_eval` 任务:`task=unified task.benchmark=PowerSystems/MicrogridBatteryDispatch` + - 快速运行:`python -m frontier_eval task=unified task.benchmark=PowerSystems/MicrogridBatteryDispatch algorithm.iterations=0` + - 简介:在分时电价、需量电费、电池退化和物理功率约束下优化商业微电网电池调度 diff --git a/benchmarks/Robotics/AGVWarehouseRouting/README.md b/benchmarks/Robotics/AGVWarehouseRouting/README.md new file mode 100644 index 00000000..303512cf --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/README.md @@ -0,0 +1,44 @@ +# AGVWarehouseRouting + +Optimize the pick sequence for an automated guided vehicle (AGV) in warehouse +aisles with obstacles, congestion zones, turn penalties, and route-length limits. + +## Files + +- `Task.md`: task contract and scoring details. +- `scripts/init.py`: editable seed routing heuristic. +- `verification/evaluator.py`: deterministic route simulator and scorer. +- `verification/requirements.txt`: evaluator dependencies. +- `frontier_eval/`: unified-task metadata. + +## Candidate Interface + +Edit `scripts/init.py` only. The evaluator imports: + +```python +plan_order(instance: dict) -> list[int] +``` + +The returned list must contain every pick id exactly once. The evaluator computes +the least-cost path between consecutive stops on the fixed grid and scores the +resulting total travel cost. + +## Quick Run + +From this directory: + +```bash +python verification/evaluator.py scripts/init.py +``` + +From the repository root: + +```bash +python -m frontier_eval \ + task=unified \ + task.benchmark=Robotics/AGVWarehouseRouting \ + algorithm=openevolve \ + algorithm.iterations=0 +``` + +No GPU, Docker, API key, or external assets are required. diff --git a/benchmarks/Robotics/AGVWarehouseRouting/Task.md b/benchmarks/Robotics/AGVWarehouseRouting/Task.md new file mode 100644 index 00000000..6600f39a --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/Task.md @@ -0,0 +1,49 @@ +# AGV Warehouse Routing + +## Background + +Warehouses often run fleets of automated guided vehicles to pick parts, bins, or +parcels. Even for one vehicle, the best visit order depends on aisle topology, +blocked cells, turn cost, congestion zones, and required return location. Better +ordering reduces cycle time and battery usage without changing warehouse layout. + +## Objective + +Given a fixed warehouse instance, return the order in which the AGV should visit +all required pick locations before ending at the outbound station. The evaluator +uses a deterministic shortest-path model between visits and sums travel, +congestion, and turn costs. + +## Candidate API + +The evaluator imports `plan_order(instance)` from `scripts/init.py`. + +`instance` contains: + +- `instance_id` +- `rows`, `cols` +- `grid`: list of strings, where `#` marks blocked cells +- `start`: `[row, col]` +- `goal`: `[row, col]` +- `picks`: list of `{id, row, col, priority}` +- `traffic`: list of `{row, col, extra_cost}` +- `turn_penalty` + +Return a list of pick ids. Every id must appear exactly once. + +## Constraints + +- Do not import external packages. +- Do not read or write files. +- Keep the public `plan_order(instance)` interface. +- Keep all editable logic inside the EVOLVE block. +- The policy must be deterministic for the same input instance. + +## Scoring + +The evaluator computes the least-cost grid path from start through the returned +pick sequence and then to goal. Cell traversal cost includes base travel cost, +traffic surcharge, and heading-change penalties. + +`combined_score = -mean(route_cost across instances)`, so higher is better. +Invalid permutations or unreachable routes receive an invalid score. diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/agent_files.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/agent_files.txt new file mode 100644 index 00000000..e60e972a --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/agent_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +scripts/init.py +verification/evaluator.py +frontier_eval/constraints.txt diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/artifact_files.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/artifact_files.txt new file mode 100644 index 00000000..76dc893a --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/artifact_files.txt @@ -0,0 +1,2 @@ +metrics.json +artifacts.json diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/candidate_destination.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/candidate_destination.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/candidate_destination.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/constraints.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/constraints.txt new file mode 100644 index 00000000..115db7d1 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/constraints.txt @@ -0,0 +1,6 @@ +AGVWarehouseRouting constraints: +1) Modify only scripts/init.py. +2) Keep plan_order(instance) as the public entrypoint. +3) Return a permutation containing every pick id exactly once. +4) Do not use external packages, file I/O, network access, or randomness. +5) Keep EVOLVE-BLOCK markers intact and place editable routing logic inside them. diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/copy_files.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/copy_files.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/copy_files.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_command.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_command.txt new file mode 100644 index 00000000..527d23b5 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_command.txt @@ -0,0 +1 @@ +{python} verification/evaluator.py {candidate} --metrics-out metrics.json --artifacts-out artifacts.json diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_cwd.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_cwd.txt new file mode 100644 index 00000000..9c558e35 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/eval_cwd.txt @@ -0,0 +1 @@ +. diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/initial_program.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/initial_program.txt new file mode 100644 index 00000000..b9411b3d --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/initial_program.txt @@ -0,0 +1 @@ +scripts/init.py diff --git a/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/readonly_files.txt b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/readonly_files.txt new file mode 100644 index 00000000..c3f5eb23 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/frontier_eval/readonly_files.txt @@ -0,0 +1,5 @@ +README.md +Task.md +verification/evaluator.py +verification/requirements.txt +frontier_eval/ diff --git a/benchmarks/Robotics/AGVWarehouseRouting/scripts/init.py b/benchmarks/Robotics/AGVWarehouseRouting/scripts/init.py new file mode 100644 index 00000000..48489bb6 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/scripts/init.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from typing import Any + + +def _manhattan(a: tuple[int, int], b: tuple[int, int]) -> int: + return abs(a[0] - b[0]) + abs(a[1] - b[1]) + + +def plan_order(instance: dict[str, Any]) -> list[int]: + """Return a permutation of pick ids for the AGV to visit.""" + + # EVOLVE-BLOCK-START + current = tuple(int(x) for x in instance["start"]) + remaining = [ + { + "id": int(item["id"]), + "pos": (int(item["row"]), int(item["col"])), + "priority": float(item.get("priority", 1.0)), + } + for item in instance["picks"] + ] + goal = tuple(int(x) for x in instance["goal"]) + order: list[int] = [] + + while remaining: + def score(item: dict[str, Any]) -> float: + dist = _manhattan(current, item["pos"]) + finish_bias = 0.20 * _manhattan(item["pos"], goal) + priority_bonus = 2.0 * item["priority"] + return dist + finish_bias - priority_bonus + + best = min(remaining, key=score) + order.append(int(best["id"])) + current = best["pos"] + remaining = [item for item in remaining if int(item["id"]) != int(best["id"])] + + return order + # EVOLVE-BLOCK-END + + +if __name__ == "__main__": + demo = { + "start": [0, 0], + "goal": [0, 4], + "picks": [ + {"id": 0, "row": 2, "col": 1, "priority": 1.0}, + {"id": 1, "row": 3, "col": 3, "priority": 1.0}, + ], + } + print(plan_order(demo)) diff --git a/benchmarks/Robotics/AGVWarehouseRouting/verification/evaluator.py b/benchmarks/Robotics/AGVWarehouseRouting/verification/evaluator.py new file mode 100644 index 00000000..4e1df617 --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/verification/evaluator.py @@ -0,0 +1,296 @@ +from __future__ import annotations + +import argparse +import heapq +import importlib.util +import json +import math +import statistics +import sys +import time +import traceback +from pathlib import Path +from types import ModuleType +from typing import Any + + +INVALID_COMBINED_SCORE = -1e18 +DIRS = [(-1, 0), (0, 1), (1, 0), (0, -1)] + + +def _write_json(path: str | None, payload: dict[str, Any]) -> None: + if not path: + return + target = Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8") + + +def _load_candidate(candidate_path: Path) -> ModuleType: + spec = importlib.util.spec_from_file_location("agv_candidate", candidate_path) + if spec is None or spec.loader is None: + raise ImportError(f"failed to load candidate module from {candidate_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _traffic_map(entries: list[dict[str, Any]]) -> dict[tuple[int, int], float]: + return { + (int(item["row"]), int(item["col"])): float(item["extra_cost"]) + for item in entries + } + + +def _instances() -> list[dict[str, Any]]: + return [ + { + "instance_id": "narrow_aisles_morning", + "grid": [ + "..............", + ".####..####...", + "..............", + "...####..####.", + "..............", + ".####..####...", + "..............", + ], + "start": [0, 0], + "goal": [6, 13], + "picks": [ + {"id": 0, "row": 2, "col": 2, "priority": 1.0}, + {"id": 1, "row": 0, "col": 9, "priority": 1.3}, + {"id": 2, "row": 4, "col": 5, "priority": 0.8}, + {"id": 3, "row": 6, "col": 10, "priority": 1.1}, + {"id": 4, "row": 2, "col": 12, "priority": 0.9}, + ], + "traffic": [ + {"row": 2, "col": 6, "extra_cost": 2.2}, + {"row": 4, "col": 7, "extra_cost": 1.8}, + {"row": 6, "col": 8, "extra_cost": 1.4}, + ], + "turn_penalty": 0.35, + }, + { + "instance_id": "crossdock_afternoon", + "grid": [ + "................", + "..####....####..", + "................", + ".##..######..##.", + "................", + "..####....####..", + "................", + "....###..###....", + "................", + ], + "start": [8, 0], + "goal": [0, 15], + "picks": [ + {"id": 0, "row": 6, "col": 3, "priority": 1.2}, + {"id": 1, "row": 2, "col": 1, "priority": 1.0}, + {"id": 2, "row": 0, "col": 6, "priority": 1.4}, + {"id": 3, "row": 4, "col": 14, "priority": 0.7}, + {"id": 4, "row": 8, "col": 11, "priority": 1.1}, + {"id": 5, "row": 2, "col": 13, "priority": 0.9}, + ], + "traffic": [ + {"row": 4, "col": 7, "extra_cost": 2.8}, + {"row": 4, "col": 8, "extra_cost": 2.8}, + {"row": 2, "col": 8, "extra_cost": 1.5}, + {"row": 6, "col": 8, "extra_cost": 1.5}, + ], + "turn_penalty": 0.42, + }, + { + "instance_id": "returns_lane_congestion", + "grid": [ + ".............", + ".###.###.###.", + ".............", + ".###.....###.", + ".............", + ".###.....###.", + ".............", + ".###.###.###.", + ".............", + ], + "start": [4, 0], + "goal": [4, 12], + "picks": [ + {"id": 0, "row": 0, "col": 3, "priority": 0.9}, + {"id": 1, "row": 2, "col": 8, "priority": 1.2}, + {"id": 2, "row": 6, "col": 2, "priority": 0.8}, + {"id": 3, "row": 8, "col": 9, "priority": 1.1}, + {"id": 4, "row": 3, "col": 6, "priority": 1.5}, + {"id": 5, "row": 5, "col": 8, "priority": 1.0}, + ], + "traffic": [ + {"row": 4, "col": 5, "extra_cost": 3.0}, + {"row": 4, "col": 6, "extra_cost": 3.0}, + {"row": 4, "col": 7, "extra_cost": 3.0}, + {"row": 2, "col": 6, "extra_cost": 1.2}, + {"row": 6, "col": 6, "extra_cost": 1.2}, + ], + "turn_penalty": 0.38, + }, + ] + + +def _public_instance(instance: dict[str, Any]) -> dict[str, Any]: + grid = list(instance["grid"]) + return { + "instance_id": instance["instance_id"], + "rows": len(grid), + "cols": len(grid[0]), + "grid": grid, + "start": list(instance["start"]), + "goal": list(instance["goal"]), + "picks": [dict(item) for item in instance["picks"]], + "traffic": [dict(item) for item in instance["traffic"]], + "turn_penalty": float(instance["turn_penalty"]), + } + + +def _shortest_cost( + grid: list[str], + traffic: dict[tuple[int, int], float], + start: tuple[int, int], + goal: tuple[int, int], + turn_penalty: float, +) -> float: + rows = len(grid) + cols = len(grid[0]) + pq: list[tuple[float, int, int, int]] = [] + best: dict[tuple[int, int, int], float] = {} + for heading in range(4): + state = (start[0], start[1], heading) + best[state] = 0.0 + heapq.heappush(pq, (0.0, start[0], start[1], heading)) + + while pq: + cost, row, col, heading = heapq.heappop(pq) + if cost > best.get((row, col, heading), math.inf) + 1e-12: + continue + if (row, col) == goal: + return cost + for next_heading, (dr, dc) in enumerate(DIRS): + nr, nc = row + dr, col + dc + if nr < 0 or nr >= rows or nc < 0 or nc >= cols: + continue + if grid[nr][nc] == "#": + continue + turn_cost = 0.0 if next_heading == heading else turn_penalty + step_cost = 1.0 + traffic.get((nr, nc), 0.0) + turn_cost + new_cost = cost + step_cost + key = (nr, nc, next_heading) + if new_cost + 1e-12 < best.get(key, math.inf): + best[key] = new_cost + heapq.heappush(pq, (new_cost, nr, nc, next_heading)) + return math.inf + + +def _coerce_order(raw: Any) -> list[int]: + if isinstance(raw, dict): + raw = raw.get("order") + if not isinstance(raw, (list, tuple)): + raise ValueError("plan_order must return a list of pick ids") + order: list[int] = [] + for item in raw: + if isinstance(item, bool): + raise ValueError("pick ids must be integers, not booleans") + order.append(int(item)) + return order + + +def _score_instance(plan_order: Any, instance: dict[str, Any]) -> dict[str, Any]: + public = _public_instance(instance) + order = _coerce_order(plan_order(public)) + expected = {int(item["id"]) for item in instance["picks"]} + if set(order) != expected or len(order) != len(expected): + raise ValueError( + f"invalid pick permutation for {instance['instance_id']}: expected {sorted(expected)}, got {order}" + ) + + pick_by_id = {int(item["id"]): (int(item["row"]), int(item["col"])) for item in instance["picks"]} + stops = [tuple(instance["start"])] + [pick_by_id[item] for item in order] + [tuple(instance["goal"])] + traffic = _traffic_map(instance["traffic"]) + total_cost = 0.0 + leg_costs: list[float] = [] + for src, dst in zip(stops, stops[1:]): + leg = _shortest_cost( + list(instance["grid"]), + traffic, + (int(src[0]), int(src[1])), + (int(dst[0]), int(dst[1])), + float(instance["turn_penalty"]), + ) + if not math.isfinite(leg): + raise ValueError(f"unreachable leg {src}->{dst} in {instance['instance_id']}") + leg_costs.append(leg) + total_cost += leg + + priority_weighted_lateness = 0.0 + for index, pick_id in enumerate(order): + priority = next(float(item.get("priority", 1.0)) for item in instance["picks"] if int(item["id"]) == pick_id) + priority_weighted_lateness += priority * index + total_cost += 0.15 * priority_weighted_lateness + return { + "instance_id": instance["instance_id"], + "order": order, + "route_cost": total_cost, + "leg_costs": leg_costs, + "priority_weighted_lateness": priority_weighted_lateness, + } + + +def evaluate(candidate_path: str) -> tuple[dict[str, float], dict[str, Any]]: + module = _load_candidate(Path(candidate_path).resolve()) + plan_order = getattr(module, "plan_order", None) + if plan_order is None: + raise AttributeError("candidate must define plan_order(instance)") + case_results = [_score_instance(plan_order, instance) for instance in _instances()] + mean_cost = statistics.fmean(float(item["route_cost"]) for item in case_results) + metrics = { + "combined_score": -float(mean_cost), + "valid": 1.0, + "mean_route_cost": float(mean_cost), + "max_route_cost": max(float(item["route_cost"]) for item in case_results), + } + artifacts = { + "case_results": case_results, + "score_direction": "higher_is_better", + } + return metrics, artifacts + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("candidate") + parser.add_argument("--metrics-out", default="metrics.json") + parser.add_argument("--artifacts-out", default="artifacts.json") + args = parser.parse_args(argv) + + start = time.time() + try: + metrics, artifacts = evaluate(args.candidate) + except Exception as exc: + metrics = { + "combined_score": INVALID_COMBINED_SCORE, + "valid": 0.0, + "runtime_s": float(time.time() - start), + } + artifacts = { + "error_message": str(exc), + "traceback": traceback.format_exc(), + } + metrics["runtime_s"] = float(time.time() - start) + artifacts["candidate_path"] = str(Path(args.candidate).resolve()) + _write_json(args.metrics_out, metrics) + _write_json(args.artifacts_out, artifacts) + print(json.dumps(metrics, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/benchmarks/Robotics/AGVWarehouseRouting/verification/requirements.txt b/benchmarks/Robotics/AGVWarehouseRouting/verification/requirements.txt new file mode 100644 index 00000000..821e76fa --- /dev/null +++ b/benchmarks/Robotics/AGVWarehouseRouting/verification/requirements.txt @@ -0,0 +1 @@ +# Python standard library only. diff --git a/benchmarks/Robotics/README.md b/benchmarks/Robotics/README.md index b3e834a5..28e1eab1 100644 --- a/benchmarks/Robotics/README.md +++ b/benchmarks/Robotics/README.md @@ -7,6 +7,7 @@ This domain contains robotics control and planning tasks for unified evaluation. - `CoFlyersVasarhelyiTuning` - Unified benchmark: `task=coflyers_vasarhelyi_tuning` - Quick run: `python -m frontier_eval task=coflyers_vasarhelyi_tuning algorithm.iterations=0` +- `AGVWarehouseRouting` - `DynamicObstacleAvoidanceNavigation` - `PIDTuning` - `QuadrupedGaitOptimization` @@ -15,6 +16,7 @@ This domain contains robotics control and planning tasks for unified evaluation. ### Unified quick runs +- `AGVWarehouseRouting`: `python -m frontier_eval task=unified task.benchmark=Robotics/AGVWarehouseRouting algorithm.iterations=0` - `DynamicObstacleAvoidanceNavigation`: `python -m frontier_eval task=unified task.benchmark=Robotics/DynamicObstacleAvoidanceNavigation algorithm.iterations=0` - `PIDTuning`: `python -m frontier_eval task=unified task.benchmark=Robotics/PIDTuning algorithm.iterations=0` - `QuadrupedGaitOptimization`: `.venvs/frontier-eval-driver/bin/python -m frontier_eval task=unified task.benchmark=Robotics/QuadrupedGaitOptimization task.runtime.env_name=frontier-v1-main algorithm.iterations=0`