Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Pytest configuration ensuring the src package is importable."""

import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent
SRC = ROOT / "src"
if str(SRC) not in sys.path:
sys.path.insert(0, str(SRC))
20 changes: 19 additions & 1 deletion src/randomize_evolve/evaluators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,23 @@
Evaluator,
EvaluatorConfig,
)
from randomize_evolve.evaluators.packet_switching import (
PacketSwitchingEvaluation,
PacketSwitchingEvaluator,
PacketSwitchingEvaluatorConfig,
ScenarioConfig,
ScenarioResult,
default_scenarios,
)

__all__ = ["Evaluator", "EvaluatorConfig", "EvaluationResult"]
__all__ = [
"Evaluator",
"EvaluatorConfig",
"EvaluationResult",
"PacketSwitchingEvaluator",
"PacketSwitchingEvaluatorConfig",
"PacketSwitchingEvaluation",
"ScenarioConfig",
"ScenarioResult",
"default_scenarios",
]
183 changes: 183 additions & 0 deletions src/randomize_evolve/evaluators/packet_switching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""Evaluator for packet switching scheduling strategies."""

from dataclasses import dataclass, field
from typing import Callable, List, Optional, Sequence

from randomize_evolve.packet_switching import RoundRobinScheduler, SwitchScheduler
from randomize_evolve.traffic import (
SimulationResult,
SwitchTrafficSimulator,
TrafficPatternConfig,
TrafficPatternType,
build_pattern,
)


SchedulerFactory = Callable[[int], SwitchScheduler]


@dataclass
class ScenarioConfig:
"""Configures a single traffic scenario for evaluation."""

name: str
pattern: TrafficPatternConfig
time_slots: int = 1500
warmup_slots: int = 200
queue_limit: Optional[int] = None
throughput_weight: float = 0.6
fairness_weight: float = 0.3
flow_fairness_weight: float = 0.1
drop_weight: float = 0.4
seed_offset: int = 0


@dataclass
class ScenarioResult:
"""Stores the outcome of running a scheduler in a scenario."""

config: ScenarioConfig
metrics: SimulationResult
score: float


@dataclass
class PacketSwitchingEvaluation:
"""Aggregated evaluation result across scenarios."""

score: float
scenario_results: List[ScenarioResult]
success: bool


@dataclass
class PacketSwitchingEvaluatorConfig:
"""High level configuration for the packet switching evaluator."""

ports: int = 8
scenarios: Sequence[ScenarioConfig] = field(default_factory=list)
seed: int = 7

def __post_init__(self) -> None:
if not self.scenarios:
self.scenarios = default_scenarios()


def default_scenarios() -> List[ScenarioConfig]:
"""Return a curated set of default traffic scenarios."""

return [
ScenarioConfig(
name="uniform-medium",
pattern=TrafficPatternConfig(
pattern_type=TrafficPatternType.UNIFORM,
offered_load=0.6,
),
throughput_weight=0.7,
fairness_weight=0.25,
flow_fairness_weight=0.05,
drop_weight=0.3,
),
ScenarioConfig(
name="bursty",
pattern=TrafficPatternConfig(
pattern_type=TrafficPatternType.BURSTY,
offered_load=0.4,
burst_rate=5,
burst_length=6,
burst_probability=0.12,
),
throughput_weight=0.65,
fairness_weight=0.25,
flow_fairness_weight=0.1,
drop_weight=0.35,
),
ScenarioConfig(
name="hotspot-heavy",
pattern=TrafficPatternConfig(
pattern_type=TrafficPatternType.HOTSPOT,
offered_load=0.75,
hotspot_probability=0.65,
),
throughput_weight=0.55,
fairness_weight=0.35,
flow_fairness_weight=0.1,
drop_weight=0.45,
),
ScenarioConfig(
name="heavy-cycle",
pattern=TrafficPatternConfig(
pattern_type=TrafficPatternType.HEAVY_LOAD,
heavy_load=0.98,
light_load=0.35,
heavy_duration=80,
light_duration=40,
),
throughput_weight=0.6,
fairness_weight=0.3,
flow_fairness_weight=0.1,
drop_weight=0.5,
),
]


class PacketSwitchingEvaluator:
"""Callable evaluator compatible with the OpenEvolve workflow."""

def __init__(self, config: Optional[PacketSwitchingEvaluatorConfig] = None) -> None:
self.config = config or PacketSwitchingEvaluatorConfig()

def __call__(self, factory: Optional[SchedulerFactory] = None) -> PacketSwitchingEvaluation:
factory = factory or (lambda ports: RoundRobinScheduler(ports, ports))
scenario_results: List[ScenarioResult] = []
total_weight = 0.0
total_score = 0.0

for index, scenario in enumerate(self.config.scenarios):
scheduler = factory(self.config.ports)
pattern = build_pattern(scenario.pattern)
simulator = SwitchTrafficSimulator(
pattern,
num_inputs=self.config.ports,
num_outputs=self.config.ports,
time_slots=scenario.time_slots,
warmup_slots=scenario.warmup_slots,
queue_limit=scenario.queue_limit,
seed=self.config.seed + scenario.seed_offset + index,
)
metrics = simulator.run(scheduler)
scenario_score, scenario_weight = self._score(metrics, scenario)
total_score += scenario_score
total_weight += scenario_weight
scenario_results.append(
ScenarioResult(
config=scenario,
metrics=metrics,
score=scenario_score / scenario_weight if scenario_weight else 0.0,
)
)

aggregate_score = total_score / total_weight if total_weight else float("inf")
success = bool(scenario_results)
return PacketSwitchingEvaluation(
score=aggregate_score,
scenario_results=scenario_results,
success=success,
)

def _score(self, metrics: SimulationResult, scenario: ScenarioConfig) -> tuple[float, float]:
weights = (
scenario.throughput_weight,
scenario.fairness_weight,
scenario.flow_fairness_weight,
scenario.drop_weight,
)
total_weight = sum(weights)
if total_weight <= 0:
raise ValueError("Scenario weight configuration must be positive")
throughput_term = scenario.throughput_weight * (1.0 - metrics.throughput)
fairness_term = scenario.fairness_weight * (1.0 - metrics.fairness_inputs)
flow_fairness_term = scenario.flow_fairness_weight * (1.0 - metrics.fairness_flows)
drop_term = scenario.drop_weight * metrics.drop_rate
scenario_score = throughput_term + fairness_term + flow_fairness_term + drop_term
return scenario_score, total_weight
8 changes: 8 additions & 0 deletions src/randomize_evolve/packet_switching/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Packet switching scheduling strategies and helpers."""

from randomize_evolve.packet_switching.schedulers import (
RoundRobinScheduler,
SwitchScheduler,
)

__all__ = ["RoundRobinScheduler", "SwitchScheduler"]
65 changes: 65 additions & 0 deletions src/randomize_evolve/packet_switching/schedulers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Scheduling strategies for the packet switching simulator."""

from dataclasses import dataclass
from typing import Dict, List, MutableMapping, Protocol, Sequence


class SwitchScheduler(Protocol):
"""Protocol for algorithms that compute input-output matchings."""

def select_matches(
self,
requests: Dict[int, List[int]],
time_slot: int,
queue_lengths: Sequence[int],
) -> MutableMapping[int, int]:
"""Return a mapping of input index to output index for the current slot."""


@dataclass
class RoundRobinScheduler:
"""A simple round-robin scheduler with queue length awareness."""

num_inputs: int
num_outputs: int

def __post_init__(self) -> None:
if self.num_inputs <= 0 or self.num_outputs <= 0:
raise ValueError("Switch dimensions must be positive")
self._output_priority = 0
self._output_pointers = [0 for _ in range(self.num_outputs)]

def select_matches(
self,
requests: Dict[int, List[int]],
time_slot: int,
queue_lengths: Sequence[int],
) -> MutableMapping[int, int]:
matches: Dict[int, int] = {}
used_inputs = set()
outputs_in_order = [
(self._output_priority + offset) % self.num_outputs
for offset in range(self.num_outputs)
]
self._output_priority = (self._output_priority + 1) % self.num_outputs

for output_idx in outputs_in_order:
candidates = requests.get(output_idx)
if not candidates:
continue
pointer = self._output_pointers[output_idx]
sorted_candidates = sorted(
candidates,
key=lambda idx: (
-queue_lengths[idx] if idx < len(queue_lengths) else 0,
(idx - pointer) % self.num_inputs,
),
)
for input_idx in sorted_candidates:
if input_idx in used_inputs:
continue
matches[input_idx] = output_idx
used_inputs.add(input_idx)
self._output_pointers[output_idx] = (input_idx + 1) % self.num_inputs
break
return matches
26 changes: 26 additions & 0 deletions src/randomize_evolve/traffic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Traffic generation utilities for packet switching simulations."""

from randomize_evolve.traffic.patterns import (
TrafficPattern,
TrafficPatternConfig,
TrafficPatternType,
UniformPattern,
BurstyPattern,
HotspotPattern,
HeavyLoadPattern,
build_pattern,
)
from randomize_evolve.traffic.simulator import SimulationResult, SwitchTrafficSimulator

__all__ = [
"TrafficPattern",
"TrafficPatternConfig",
"TrafficPatternType",
"UniformPattern",
"BurstyPattern",
"HotspotPattern",
"HeavyLoadPattern",
"build_pattern",
"SimulationResult",
"SwitchTrafficSimulator",
]
Loading