Simulation Guide

This document explains how the SimulationEngine works, from initialization to completion.

Overview

The SimulationEngine is the main entry point for running FUSION simulations. It coordinates:

  • Network topology creation

  • Request generation (Poisson arrival process)

  • Request processing (via SDNController or SDNOrchestrator)

  • Metrics collection (via SimStats)

  • Results persistence (via StatsPersistence)

SimulationEngine
     |
     +-- create_topology()     # Initialize network
     |
     +-- run()                 # Main simulation loop
     |    |
     |    +-- generate_requests()
     |    |
     |    +-- for each iteration:
     |    |    |
     |    |    +-- init_iter_stats()
     |    |    |
     |    |    +-- for each request:
     |    |    |    |
     |    |    |    +-- SDNController.allocate()  # Legacy
     |    |    |    |   OR
     |    |    |    +-- SDNOrchestrator.handle_arrival()  # v6.0+
     |    |    |    |
     |    |    |    +-- SimStats.iter_update()
     |    |    |
     |    |    +-- calculate_blocking_statistics()
     |    |    |
     |    |    +-- check confidence interval
     |    |
     |    +-- save_stats()
     |
     +-- cleanup()

SimulationEngine Class

Location:

fusion/core/simulation.py

Size:

2,209 lines (needs refactoring)

Initialization

from fusion.core import SimulationEngine

engine_props = {
    # Simulation control
    'max_iters': 10,
    'num_requests': 1000,
    'erlang': 300,
    'holding_time': 3600,

    # Network configuration
    'network': 'NSFNet',
    'cores_per_link': 7,
    'c_band': 320,

    # Algorithm selection
    'route_method': 'k_shortest_path',
    'allocation_method': 'first_fit',
    'k_paths': 3,

    # Architecture selection
    'use_orchestrator': True,  # or False for legacy
}

engine = SimulationEngine(engine_props)

Key Properties

Property

Description

engine_props

Configuration dictionary

topology

NetworkX graph of the network

sdn_controller

SDNController instance (legacy path)

orchestrator

SDNOrchestrator instance (v6.0+ path)

stats

SimStats instance for metrics

network_spectrum_dict

Current spectrum allocation state

Topology Creation

The create_topology() method initializes the network:

engine.create_topology()

What happens:

  1. Load network graph from file (e.g., NSFNet.json)

  2. Initialize spectrum dictionary for each link

  3. Set up lightpath status dictionary

  4. Create modulation format mappings

  5. Initialize failure manager (if survivability enabled)

Network spectrum dictionary structure:

# network_spectrum_dict[(src, dst)][core][slot] = request_id or 0
network_spectrum_dict = {
    ("0", "1"): {
        0: np.zeros(320, dtype=int),  # Core 0
        1: np.zeros(320, dtype=int),  # Core 1
        # ... for each core
    },
    ("0", "2"): {...},
    # ... for each link
}

Request Generation

Requests are generated using a Poisson arrival process:

from fusion.core.request import generate_simulation_requests

requests = generate_simulation_requests(
    num_requests=1000,
    erlang=300,
    holding_time=3600,
    topology=topology,
    bandwidth_distribution={"100": 0.5, "200": 0.3, "400": 0.2},
    seed=42,
)

Request structure:

request = {
    'request_id': 1,
    'source': '0',
    'destination': '5',
    'bandwidth': 100,
    'arrive': 0.5,      # Arrival time
    'depart': 3600.5,   # Departure time
    'request_type': 'arrival',  # or 'departure'
}

Arrival process:

  • Inter-arrival times: Exponential distribution with rate = erlang / holding_time

  • Holding times: Exponential distribution with mean = holding_time

  • Node pairs: Uniform random selection

  • Bandwidth: According to bandwidth_distribution

Main Simulation Loop

The run() method executes the main simulation:

completed_iterations = engine.run()

Flow:

for iteration in range(max_iters):
    |
    +-- Initialize iteration statistics
    |
    +-- Reset network state (spectrum, lightpaths)
    |
    +-- for request in sorted_requests:
    |    |
    |    +-- if request_type == 'arrival':
    |    |    |
    |    |    +-- [Legacy] sdn_controller.allocate()
    |    |    |   OR
    |    |    +-- [Orchestrator] orchestrator.handle_arrival()
    |    |    |
    |    |    +-- stats.iter_update()
    |    |
    |    +-- elif request_type == 'departure':
    |         |
    |         +-- Release spectrum
    |         +-- Remove lightpath
    |
    +-- Calculate blocking statistics
    |
    +-- Check confidence interval
    |    |
    |    +-- If converged: break
    |
    +-- Save iteration results

Feature Flag Resolution

The simulation chooses between legacy and orchestrator based on:

  1. Environment variable: FUSION_USE_ORCHESTRATOR

  2. Config parameter: use_orchestrator

  3. Default: False (legacy)

def _resolve_use_orchestrator(self) -> bool:
    """Resolve which architecture to use."""
    # 1. Check environment variable (highest priority)
    env_value = os.environ.get('FUSION_USE_ORCHESTRATOR')
    if env_value is not None:
        return env_value.lower() in ('1', 'true', 'yes')

    # 2. Check config parameter
    return self.engine_props.get('use_orchestrator', False)

Request Processing

Legacy Path (SDNController)

# In simulation loop
if not use_orchestrator:
    # Set up request in sdn_props
    self.sdn_props.source = request['source']
    self.sdn_props.destination = request['destination']
    self.sdn_props.bandwidth = request['bandwidth']

    # Process request
    self.sdn_controller.allocate()

    # Check result
    if self.sdn_props.was_routed:
        # Update network state
        self._update_network_after_allocation()
    else:
        # Track blocking
        self._handle_blocked_request()

Orchestrator Path (SDNOrchestrator)

# In simulation loop
if use_orchestrator:
    # Create Request object
    request_obj = Request(
        request_id=request['request_id'],
        source=request['source'],
        destination=request['destination'],
        bandwidth_gbps=request['bandwidth'],
        arrive_time=request['arrive'],
        depart_time=request['depart'],
    )

    # Create network state snapshot
    network_state = NetworkState(
        topology=self.topology,
        network_spectrum_dict=self.network_spectrum_dict,
        lightpath_status_dict=self.lightpath_status_dict,
    )

    # Process request
    result = self.orchestrator.handle_arrival(request_obj, network_state)

    # Handle result
    if result.success:
        self._apply_allocation(result, request_obj)
    else:
        self._handle_blocked_request(result.block_reason)

Metrics Collection

After each request, metrics are updated:

# Update statistics
self.stats.iter_update(
    request_data=request,
    sdn_data=self.sdn_props,  # or result for orchestrator
    network_spectrum_dict=self.network_spectrum_dict,
)

At the end of each iteration:

# Calculate iteration statistics
self.stats.calculate_blocking_statistics()
self.stats.finalize_iteration_statistics()

# Check convergence
if self.stats.calculate_confidence_interval():
    logger.info("Confidence interval reached, stopping early")
    break

Results Persistence

Results are saved at the end of simulation:

# Create persistence handler
persistence = StatsPersistence(self.engine_props, self.sim_info)

# Prepare statistics
blocking_stats = {
    'block_mean': self.stats.blocking_mean,
    'block_variance': self.stats.blocking_variance,
    'block_ci': self.stats.confidence_interval,
    'iteration': current_iteration,
}

# Save to file
persistence.save_stats(
    stats_dict=self.stats.get_stats_dict(),
    stats_props=self.stats.stats_props,
    blocking_stats=blocking_stats,
)

Multiprocessing Support

Note

Multiprocessing support is currently being worked on in v6.0 and is not fully functional yet. The information below describes the intended design.

FUSION supports parallel simulation across multiple processes:

from fusion.cli.run_sim import run_simulation

# Configuration with Erlang range
engine_props = {
    'erlang_start': 300,
    'erlang_stop': 600,
    'erlang_step': 100,
    'thread_erlangs': True,  # Enable parallel execution
    # ... other config
}

# Run parallel simulations
run_simulation(engine_props)

How it works:

  1. Main process creates worker processes

  2. Each process can run multiple Erlang values (not one per process)

  3. Each process saves results independently to separate files

  4. Results are not automatically aggregated - each process outputs its own files

Failure Manager Integration

Note

Failure manager integration is currently being worked on in v6.0 and is not fully functional yet. The information below describes the intended design.

For survivability experiments, the simulation integrates with FailureManager:

# Enable failures in config
engine_props['failure_enabled'] = True
engine_props['failure_type'] = 'link'  # link, node, srlg, geo
engine_props['t_fail_arrival_index'] = 500  # Fail after 500 arrivals
engine_props['t_repair_after_arrivals'] = 100  # Repair after 100 more

Failure handling flow:

Request arrival
     |
     +-- Check if failure should trigger
     |    |
     |    +-- If t_fail reached: inject_failure()
     |
     +-- Process request (with failed links removed)
     |
     +-- Check if repair should trigger
          |
          +-- If t_repair reached: repair_failure()

Common Configuration Options

Option

Default

Description

max_iters

10

Maximum simulation iterations

num_requests

1000

Requests per iteration

erlang

300

Traffic load in Erlangs

holding_time

3600

Average request duration (seconds)

network

“NSFNet”

Network topology name

cores_per_link

7

Fiber cores per link

c_band

320

C-band spectrum slots

k_paths

3

Number of candidate paths

print_step

0

Progress reporting interval

save_snapshots

False

Save periodic state snapshots

ci_target

5.0

Confidence interval target (%)

Debugging Tips

Enable verbose output:

engine_props['print_step'] = 10  # Print every 10 requests

Save snapshots for analysis:

engine_props['save_snapshots'] = True
engine_props['snapshot_interval'] = 100  # Every 100 requests

Use deterministic seed:

engine_props['seed'] = 42  # Reproducible results

Run single iteration:

engine_props['max_iters'] = 1
engine_props['num_requests'] = 100  # Small for quick testing

See Also