.. _domain-lifecycle: ================================= Object Lifecycle in the Pipeline ================================= This document explains how domain objects are created, modified, and accessed as a request flows through the simulation pipeline. Understanding this lifecycle is essential for debugging, extending the simulator, and implementing new features. Overview: The Big Picture ========================= The following diagram shows the complete lifecycle of domain objects during request processing: .. code-block:: text STARTUP PHASE ============= engine_props (dict) | v +-------------------+ | SimulationConfig | <-- Created once, immutable +-------------------+ | | from_config() v +-------------------+ | NetworkState | <-- Created with empty spectrum +-------------------+ REQUEST PROCESSING PHASE (for each request) =========================================== +-------------------+ | Request | <-- Generated with source, dest, bandwidth +-------------------+ | v +-------------------+ +-------------------+ | SDNOrchestrator |<--->| NetworkState | <-- Passed per-call +-------------------+ | (mutable) | | +-------------------+ | +-----> Stage 1: Grooming -----> GroomingResult | +-----> Stage 2: Routing ------> RouteResult | +-----> Stage 3: Spectrum -----> SpectrumResult | +-----> Stage 4: SNR ----------> SNRResult | +-----> Stage 5: Slicing ------> SlicingResult | +-----> Stage 6: Protection ---> ProtectionResult | v +-------------------+ | AllocationResult | <-- Final outcome (immutable) +-------------------+ | | if success v +-------------------+ +-------------------+ | NetworkState |---->| Lightpath | <-- New lightpath created | .create_lightpath | | (added to state) | +-------------------+ +-------------------+ DEPARTURE PHASE =============== +-------------------+ | Request (DEPART) | +-------------------+ | v +-------------------+ +-------------------+ | NetworkState |---->| Lightpath | | .release_bandwidth| | (capacity freed) | +-------------------+ +-------------------+ | | if lightpath empty v +-------------------+ | NetworkState | <-- Lightpath removed, spectrum released | .remove_lightpath | +-------------------+ Startup Phase ============= At simulation startup, configuration is parsed and network state is initialized. Step 1: Configuration Creation ------------------------------ .. code-block:: python # CLI/Config parsing produces engine_props dict engine_props = { 'network': 'USbackbone60', 'cores_per_link': 7, 'c_band': 320, # ... many more options } # SimulationConfig is created once (immutable) from fusion.domain import SimulationConfig config = SimulationConfig.from_engine_props(engine_props) **What happens:** 1. ``from_engine_props()`` extracts and validates all configuration values 2. ``__post_init__()`` validates configuration consistency 3. The frozen dataclass prevents any future modification Step 2: NetworkState Initialization ----------------------------------- .. code-block:: python from fusion.domain import NetworkState # Create from configuration and topology state = NetworkState.from_config(config, topology) **What happens:** 1. NetworkState creates ``LinkSpectrum`` objects for each link in topology 2. Each LinkSpectrum initializes spectrum arrays (all zeros = all free) 3. Lightpath registry is empty 4. ``next_lightpath_id`` starts at 1 **Object state after startup:** .. code-block:: text SimulationConfig (frozen) ├── network_name: "USbackbone60" ├── cores_per_link: 7 ├── band_slots: {"c": 320} └── ... (all read-only) NetworkState (mutable) ├── _topology: NetworkX Graph ├── _spectrum: { │ ("0", "1"): LinkSpectrum(all zeros), │ ("0", "2"): LinkSpectrum(all zeros), │ ... │ } ├── _lightpaths: {} (empty) └── _next_lightpath_id: 1 Request Arrival Processing ========================== When a request arrives, it flows through the orchestrator's pipeline stages. Stage 0: Request Generation --------------------------- .. code-block:: python from fusion.domain import Request, RequestType # Request is generated by traffic generator request = Request( request_id=42, source="0", destination="5", bandwidth_gbps=100, arrive=0.5, depart=3600.5, request_type=RequestType.ARRIVAL, ) **Request state:** ``status=PENDING`` Stage 1: Grooming Pipeline -------------------------- **Purpose:** Check if existing lightpaths can serve this request. .. code-block:: text Input Objects: ├── Request (read-only) ├── NetworkState (read-only query) └── SimulationConfig (read-only) Output Object: └── GroomingResult (immutable) **Object interactions:** .. code-block:: python # Pipeline queries NetworkState for existing lightpaths existing_lps = state.get_lightpaths_for_endpoints( request.source, request.destination ) # Check capacity on each lightpath for lp in existing_lps: if lp.can_accommodate(request.bandwidth_gbps): # Found capacity - can groom! return GroomingResult( fully_groomed=True, bandwidth_groomed_gbps=request.bandwidth_gbps, lightpaths_used=(lp.lightpath_id,), ) **Possible outcomes:** .. list-table:: :header-rows: 1 :widths: 25 75 * - Outcome - Next Step * - ``fully_groomed=True`` - Skip to allocation, use existing lightpath * - ``partially_groomed=True`` - Continue to routing with ``remaining_bandwidth_gbps`` * - No grooming possible - Continue to routing with full bandwidth Stage 2: Routing Pipeline ------------------------- **Purpose:** Find candidate paths from source to destination. .. code-block:: text Input Objects: ├── Request (read-only) ├── NetworkState.topology (read-only) ├── GroomingResult.forced_path (if partial grooming) └── SimulationConfig (read-only) Output Object: └── RouteResult (immutable) **Object interactions:** .. code-block:: python # If partial grooming, path is forced if grooming_result.forced_path: paths = (grooming_result.forced_path,) else: # Query topology for k-shortest paths paths = routing_algorithm.find_paths( state.topology, request.source, request.destination, k=config.k_paths, ) # Calculate path weights and modulation options return RouteResult( paths=paths, weights_km=(...), modulations=(...), strategy_name="k_shortest_path", ) **RouteResult structure:** .. code-block:: text RouteResult ├── paths: (("0", "2", "5"), ("0", "3", "5")) ├── weights_km: (150.0, 180.0) ├── modulations: (("QPSK", "16-QAM"), ("QPSK",)) └── is_empty: False Stage 3: Spectrum Assignment Pipeline ------------------------------------- **Purpose:** Find contiguous free spectrum on the selected path. .. code-block:: text Input Objects: ├── Request (read-only) ├── NetworkState (read-only query) ├── RouteResult (path selection) └── SimulationConfig (read-only) Output Object: └── SpectrumResult (immutable) **Object interactions:** .. code-block:: python # Try each path until one succeeds for path_idx, path in enumerate(route_result.paths): modulation = route_result.modulations[path_idx][0] # Best modulation # Calculate slots needed slots_needed = calculate_slots(request.bandwidth_gbps, modulation) # Query NetworkState for free spectrum for core in range(config.cores_per_link): slot = state.find_first_fit(path, core, config.band, slots_needed) if slot is not None: return SpectrumResult( is_free=True, start_slot=slot, end_slot=slot + slots_needed, core=core, band="c", modulation=modulation, slots_needed=slots_needed, ) # No spectrum found on any path return SpectrumResult(is_free=False, slots_needed=slots_needed) **NetworkState spectrum query:** .. code-block:: text For path ["0", "2", "5"]: Link ("0", "2"): ├── Core 0, Band "c": [0,0,0,1,1,1,1,0,0,0,...] └── Slots 0-3 are free Link ("2", "5"): ├── Core 0, Band "c": [0,0,0,0,0,0,0,0,1,1,...] └── Slots 0-8 are free Common free range: slots 0-3 (need intersection across all links) Stage 4: SNR Validation Pipeline -------------------------------- **Purpose:** Verify signal quality meets modulation threshold. .. code-block:: text Input Objects: ├── Request (read-only) ├── SpectrumResult (allocation details) ├── NetworkState (link parameters) └── SimulationConfig (read-only) Output Object: └── SNRResult (immutable) **Object interactions:** .. code-block:: python # Calculate SNR for the allocated spectrum snr_db = calculate_snr( path=route_result.paths[selected_path_idx], modulation=spectrum_result.modulation, core=spectrum_result.core, network_state=state, ) # Check against threshold required_snr = get_required_snr(spectrum_result.modulation) if snr_db >= required_snr: return SNRResult( passed=True, snr_db=snr_db, required_snr_db=required_snr, margin_db=snr_db - required_snr, ) else: return SNRResult( passed=False, snr_db=snr_db, required_snr_db=required_snr, ) Stage 5: Slicing Pipeline (if enabled) -------------------------------------- **Purpose:** Split request across multiple lightpaths if standard allocation fails. .. code-block:: text Input Objects: ├── Request (bandwidth requirement) ├── RouteResult (all candidate paths) ├── NetworkState (spectrum queries) └── SimulationConfig (read-only) Output Object: └── SlicingResult (immutable) **Object interactions:** .. code-block:: python # If standard allocation failed, try slicing slice_bandwidth = request.bandwidth_gbps // max_slices lightpath_ids = [] for slice_num in range(max_slices): # Try to allocate each slice spectrum = find_spectrum_for_slice(...) if spectrum.is_free: lp_id = state.create_lightpath(...) # Creates lightpath lightpath_ids.append(lp_id) if len(lightpath_ids) == max_slices: return SlicingResult( success=True, num_slices=max_slices, slice_bandwidth_gbps=slice_bandwidth, lightpath_ids=tuple(lightpath_ids), ) Stage 6: Protection Pipeline (if enabled) ----------------------------------------- **Purpose:** Establish disjoint primary and backup paths for 1+1 protection. .. code-block:: text Input Objects: ├── Request (protection requirement) ├── RouteResult (must include backup_paths) ├── NetworkState (spectrum for both paths) └── SimulationConfig (read-only) Output Object: └── ProtectionResult (immutable) Final: AllocationResult Assembly -------------------------------- .. code-block:: python # Assemble final result from all pipeline outputs if spectrum_result.is_free and snr_result.passed: return AllocationResult( success=True, lightpaths_created=(new_lightpath_id,), total_bandwidth_allocated_gbps=request.bandwidth_gbps, route_result=route_result, spectrum_result=spectrum_result, snr_result=snr_result, ) else: return AllocationResult( success=False, block_reason=determine_block_reason(...), ) State Updates on Success ======================== When allocation succeeds, NetworkState is updated with the new lightpath. Creating a New Lightpath ------------------------ .. code-block:: python if result.success: lightpath_id = state.create_lightpath( path=selected_path, start_slot=spectrum_result.start_slot, end_slot=spectrum_result.end_slot, core=spectrum_result.core, band=spectrum_result.band, modulation=spectrum_result.modulation, total_bandwidth_gbps=request.bandwidth_gbps, ) **What happens inside NetworkState.create_lightpath():** .. code-block:: text 1. Create Lightpath object: Lightpath( lightpath_id=1, path=["0", "2", "5"], start_slot=0, end_slot=8, ... ) 2. Update spectrum arrays on each link: For link ("0", "2"): spectrum[core=0][band="c"][0:8] = lightpath_id For link ("2", "5"): spectrum[core=0][band="c"][0:8] = lightpath_id 3. Add to lightpath registry: _lightpaths[1] = lightpath 4. Increment next_lightpath_id: _next_lightpath_id = 2 5. Return lightpath_id Grooming onto Existing Lightpath -------------------------------- .. code-block:: python if grooming_result.fully_groomed: # Get the existing lightpath lp = state.get_lightpath(grooming_result.lightpaths_used[0]) # Allocate bandwidth to this request lp.allocate_bandwidth( request_id=request.request_id, bandwidth_gbps=request.bandwidth_gbps, ) **What happens inside Lightpath.allocate_bandwidth():** .. code-block:: text Before: ├── remaining_bandwidth_gbps: 100 └── request_allocations: {} After: ├── remaining_bandwidth_gbps: 0 └── request_allocations: {42: 100} Request Departure Processing ============================ When a request's holding time expires, resources must be released. .. code-block:: text +-------------------+ | Request (DEPART) | | request_id=42 | +-------------------+ | v +-------------------+ | Find lightpath | | for request | +-------------------+ | v +-------------------+ +-------------------+ | Lightpath | | NetworkState | | .release_bandwidth| | (may remove LP) | +-------------------+ +-------------------+ Releasing Bandwidth ------------------- .. code-block:: python # Find the lightpath serving this request lp = find_lightpath_for_request(request.request_id, state) # Release the bandwidth released_bw = lp.release_bandwidth(request.request_id) # Check if lightpath is now empty if lp.is_empty: # Remove lightpath entirely state.remove_lightpath(lp.lightpath_id) **What happens inside NetworkState.remove_lightpath():** .. code-block:: text 1. Get lightpath from registry: lp = _lightpaths[1] 2. Release spectrum on each link: For link ("0", "2"): spectrum[core=0][band="c"][0:8] = 0 (free) For link ("2", "5"): spectrum[core=0][band="c"][0:8] = 0 (free) 3. Remove from registry: del _lightpaths[1] Complete Scenario Diagrams ========================== Scenario A: Standard Allocation (Success) ----------------------------------------- .. code-block:: text Request(id=1, src="0", dst="5", bw=100) | v [Grooming] --> GroomingResult(fully_groomed=False) | v [Routing] --> RouteResult(paths=(("0","2","5"),), ...) | v [Spectrum] --> SpectrumResult(is_free=True, slots=0-8, core=0) | v [SNR] --> SNRResult(passed=True, snr=18.5) | v AllocationResult(success=True, lightpaths_created=(1,)) | v NetworkState.create_lightpath() | v Lightpath(id=1, path=["0","2","5"], slots=0-8) + Spectrum marked on links ("0","2") and ("2","5") Scenario B: Grooming (Success) ------------------------------ .. code-block:: text Request(id=2, src="0", dst="5", bw=50) | v [Grooming] --> Found LP #1 with 100 remaining | v GroomingResult(fully_groomed=True, lightpaths_used=(1,)) | v AllocationResult(success=True, is_groomed=True) | v Lightpath #1: Before: remaining=100, request_allocations={} After: remaining=50, request_allocations={2: 50} Scenario C: Blocking (No Spectrum) ---------------------------------- .. code-block:: text Request(id=3, src="0", dst="5", bw=400) | v [Grooming] --> GroomingResult(fully_groomed=False) | v [Routing] --> RouteResult(paths=(("0","2","5"),), ...) | v [Spectrum] --> SpectrumResult(is_free=False) | v [Slicing] --> SlicingResult(success=False) (if enabled) | v AllocationResult(success=False, block_reason=NO_SPECTRUM) | v Request.status = BLOCKED NetworkState unchanged Scenario D: Slicing (Success) ----------------------------- .. code-block:: text Request(id=4, src="0", dst="5", bw=400) | v [Grooming] --> No capacity | v [Routing] --> 3 candidate paths | v [Spectrum] --> is_free=False (can't fit 400 Gbps) | v [Slicing] --> Split into 4 x 100 Gbps | +-- Create LP #2 on path 1, slots 0-8 +-- Create LP #3 on path 1, slots 8-16 +-- Create LP #4 on path 2, slots 0-8 +-- Create LP #5 on path 3, slots 0-8 | v SlicingResult(success=True, num_slices=4, lightpath_ids=(2,3,4,5)) | v AllocationResult(success=True, is_sliced=True, lightpaths_created=(2,3,4,5)) Scenario E: Protection (1+1) ---------------------------- .. code-block:: text Request(id=5, src="0", dst="5", bw=100, is_protected=True) | v [Routing] --> RouteResult( paths=(("0","2","5"),), backup_paths=(("0","3","4","5"),) ) | v [Spectrum Primary] --> SpectrumResult(is_free=True, slots=0-8) | v [Spectrum Backup] --> SpectrumResult(is_free=True, slots=0-8) | v [Protection] --> ProtectionResult( primary_established=True, backup_established=True, ) | v Lightpath #6 (working): path=["0","2","5"], is_protected=True, backup_path=["0","3","4","5"] Spectrum allocated on BOTH paths Debugging Tips ============== Understanding Object State -------------------------- When debugging, examine these key objects: .. code-block:: python # Check request state print(f"Request {req.request_id}: {req.status}, blocked={req.block_reason}") # Check lightpath state lp = state.get_lightpath(lp_id) print(f"LP {lp.lightpath_id}: util={lp.utilization:.1%}, reqs={lp.request_allocations}") # Check spectrum utilization util = state.get_spectrum_utilization() print(f"Network utilization: {util:.1%}") Common Issues ------------- **Request stuck at PENDING:** - Check if routing found any paths (``route_result.is_empty``) - Check if spectrum was found (``spectrum_result.is_free``) - Check if SNR passed (``snr_result.passed``) **Lightpath not being groomed:** - Verify lightpath exists for endpoint pair - Check ``remaining_bandwidth_gbps`` on lightpath - Confirm grooming is enabled in config **Spectrum "leak" (not being released):** - Verify ``remove_lightpath()`` is called when lightpath empties - Check that all links in path are having spectrum released See Also ======== - :doc:`objects` - Detailed field documentation - :ref:`core-orchestrator` - Pipeline implementation details - :ref:`core-module` - How core uses these domain objects