.. _snr-module: ========== SNR Module ========== Overview ======== .. admonition:: At a Glance :class: tip :Purpose: Signal-to-Noise Ratio measurement and validation for optical networks :Location: ``fusion/modules/snr/`` :Key Files: ``registry.py``, ``snr.py``, ``utils.py`` :Depends On: ``fusion.interfaces.snr``, ``fusion.core.properties`` :Used By: ``fusion.core`` (via adapters), ``fusion.pipelines`` The SNR module provides **signal quality assessment** for optical network simulations. It calculates whether proposed spectrum assignments meet the physical layer constraints required for successful data transmission. **What this module does:** - Calculates SNR for paths across multiple links - Models noise components: ASE, nonlinear (SCI, XCI), and cross-talk - Validates modulation format feasibility based on SNR thresholds - Supports multi-core fiber (MCF) with inter-core cross-talk modeling - Exposes a registry pattern for algorithm discovery and selection **When you would work here:** - Adding a new SNR calculation algorithm - Implementing advanced noise models (e.g., EGN model refinements) - Supporting new fiber types or modulation formats - Optimizing physical layer calculations for performance Understanding SNR in Optical Networks ===================================== .. important:: SNR (Signal-to-Noise Ratio) determines whether a lightpath can successfully transmit data. Higher-order modulation formats (e.g., 64-QAM) require higher SNR but use spectrum more efficiently. Lower-order formats (e.g., BPSK) tolerate lower SNR but require more spectrum. Key Concepts ------------ Signal-to-Noise Ratio (SNR) The ratio of signal power to noise power, measured in decibels (dB). Higher SNR means better signal quality. OSNR (Optical SNR) SNR measured in the optical domain before receiver processing. Typically higher than electrical SNR due to receiver noise. ASE Noise (Amplified Spontaneous Emission) Linear noise introduced by optical amplifiers (EDFAs). Accumulates with each amplifier in the path. Dominant noise source for long paths. Nonlinear Noise Noise from fiber nonlinearities that increases with signal power: - **SCI (Self-Channel Interference)**: Signal interfering with itself - **XCI (Cross-Channel Interference)**: Interference from adjacent channels - **XPM (Cross-Phase Modulation)**: Phase distortion from other channels - **FWM (Four-Wave Mixing)**: New frequencies generated by channel interaction Cross-talk (XT) In multi-core fibers, light leaks between adjacent cores. Significant for space-division multiplexing (SDM) systems. Modulation Threshold Each modulation format has a minimum required SNR: .. list-table:: :header-rows: 1 :widths: 30 20 50 * - Modulation - Required SNR - Use Case * - BPSK - 6.0 dB - Very long reach, low capacity * - QPSK - 9.0 dB - Long reach, moderate capacity * - 8-QAM - 12.0 dB - Medium reach * - 16-QAM - 15.0 dB - Metro networks * - 32-QAM - 18.0 dB - Short reach, high capacity * - 64-QAM - 21.0 dB - Data center interconnects Legacy vs. Orchestrator Architecture ==================================== Like the routing module, SNR algorithms are used by **both** architecture paths: .. code-block:: text +===========================================================================+ | SNR MODULE USAGE | +===========================================================================+ | | | use_orchestrator = False use_orchestrator = True | | (Legacy Path) (Orchestrator Path) | | | | +------------------+ +------------------+ | | | SDNController | | SDNOrchestrator | | | +--------+---------+ +--------+---------+ | | | | | | | Direct instantiation | Via SNRAdapter | | v v | | +------------------+ +------------------+ | | | fusion/core/ | | fusion/core/ | | | | snr_measurements | | adapters/ | | | | (legacy class) | | snr_adapter.py | | | +--------+---------+ +--------+---------+ | | | | | | | Uses | Wraps | | v v | | +-------------------------------------------------------+ | | | fusion/modules/snr/ | | | | | | | | StandardSNRMeasurer | | | | (future: EGNSNRMeasurer, etc.) | | | +-------------------------------------------------------+ | | | +===========================================================================+ **Key insight:** The algorithms in ``fusion/modules/snr/`` are shared by both paths. The difference is only in how they are invoked and what proxy objects wrap them. How Adapters Work with SNR -------------------------- .. list-table:: Integration Points :header-rows: 1 :widths: 25 35 40 * - Component - Location - Role * - **SNRAdapter** - ``fusion/core/adapters/snr_adapter.py`` - Wraps legacy SNR for orchestrator. Converts ``NetworkState`` to ``SDNPropsProxyForSNR``, calls legacy SNR, returns ``SNRResult``. * - **SnrMeasurements** - ``fusion/core/snr_measurements.py`` - Legacy implementation (1600+ lines). Contains EGN model, multi-band calculations, GSNR lookups. * - **StandardSNRMeasurer** - ``fusion/modules/snr/snr.py`` - Clean modular implementation following interface contract. Architecture ============ Module Structure ---------------- .. code-block:: text fusion/modules/snr/ |-- __init__.py # Public API exports (11 items) |-- registry.py # Algorithm discovery and creation |-- snr.py # StandardSNRMeasurer implementation |-- utils.py # Helper functions (file I/O, slot indexing) |-- README.md # Module documentation | |-- visualization/ # Visualization plugin (BETA) | |-- __init__.py | `-- snr_plugin.py # Plugin with 3 renderers, 7 metrics | `-- tests/ # Unit tests |-- __init__.py |-- test_snr.py |-- test_registry.py `-- test_utils.py Data Flow --------- .. code-block:: text 1. SPECTRUM ASSIGNMENT PROPOSED | | path + spectrum_info (start_slot, end_slot, core, band, modulation) v 2. SNR ALGORITHM SELECTION (via registry) | | create_snr_algorithm("standard_snr", engine_props, ...) v 3. PER-LINK SNR CALCULATION | | For each link in path: | - Calculate ASE noise | - Calculate nonlinear noise (SCI + XCI) | - Calculate cross-talk (if multi-core) | - Combine: link_snr = signal_power / total_noise v 4. PATH SNR AGGREGATION | | total_snr = 1 / sum(1/link_snr for each link) v 5. THRESHOLD VALIDATION | | required_snr = get_required_snr_threshold(modulation, reach) | acceptable = calculated_snr >= required_snr + margin v 6. RESULT RETURNED | | SNR value (dB) + acceptable (bool) Components ========== registry.py ----------- :Purpose: Centralized registry for SNR algorithm discovery and instantiation :Key Classes: ``SNRRegistry``, ``SNR_ALGORITHMS`` :Key Functions: ``create_snr_algorithm()``, ``list_snr_algorithms()``, ``get_multicore_snr_algorithms()`` The registry enables dynamic algorithm selection based on configuration: .. code-block:: python from fusion.modules.snr import ( SNRRegistry, create_snr_algorithm, list_snr_algorithms, get_multicore_snr_algorithms, ) # List available algorithms algorithms = list_snr_algorithms() # ['standard_snr'] # Get only multi-core capable algorithms mc_algorithms = get_multicore_snr_algorithms() # ['standard_snr'] # Create an algorithm instance snr_measurer = create_snr_algorithm( "standard_snr", engine_props, sdn_props, spectrum_props, route_props, ) # Calculate SNR snr_db = snr_measurer.calculate_snr(path, spectrum_info) **Registered Algorithms:** .. list-table:: :header-rows: 1 :widths: 25 20 55 * - Name - Multi-core - Description * - ``standard_snr`` - Yes - Standard SNR with ASE, nonlinear, and cross-talk noise snr.py (StandardSNRMeasurer) ---------------------------- :Purpose: Primary SNR calculation implementation :Key Class: ``StandardSNRMeasurer`` The ``StandardSNRMeasurer`` implements the ``AbstractSNRMeasurer`` interface with comprehensive noise modeling: .. code-block:: python from fusion.modules.snr import StandardSNRMeasurer measurer = StandardSNRMeasurer(engine_props, sdn_props, spectrum_props, route_props) # Calculate SNR for a path spectrum_info = { "start_slot": 10, "end_slot": 18, "core_num": 0, "band": "c", "modulation": "16-QAM", } snr_db = measurer.calculate_snr(path=[0, 1, 2, 3], spectrum_info=spectrum_info) # Check if SNR meets requirements required_snr = measurer.get_required_snr_threshold("16-QAM", reach=500.0) is_valid = measurer.is_snr_acceptable(snr_db, required_snr, margin=1.0) **Key Methods:** ``calculate_snr(path, spectrum_info) -> float`` Entry point. Calculates total path SNR by aggregating per-link SNR values using the reciprocal formula: ``1/SNR_total = sum(1/SNR_link)``. ``calculate_link_snr(source, destination, spectrum_info) -> float`` Calculates SNR for a single link by combining noise components. ``_calculate_ase_noise(link_length) -> float`` Computes ASE noise based on amplifier count (one per ~80 km span). ``_calculate_nonlinear_noise(source, dest, spectrum_info) -> dict`` Returns ``{"sci": float, "xci": float}`` for self-channel and cross-channel interference. ``_calculate_crosstalk_noise(source, dest, spectrum_info) -> float`` Computes inter-core cross-talk for multi-core fibers. **Noise Model:** The total noise power per link is: .. code-block:: text N_total = N_ase + N_sci + N_xci + N_xt Where: - N_ase = h * f * NF * B * N_amps (ASE noise) - N_sci = G_sci * P^3 (Self-channel interference) - N_xci = sum(G_xci_i * P_i * P^2) (Cross-channel interference) - N_xt = XT_coeff * N_adj * P (Cross-talk, multi-core only) utils.py -------- :Purpose: Utility functions for file I/O and slot indexing :Key Functions: ``get_loaded_files()``, ``get_slot_index()``, ``compute_response()`` ``get_loaded_files(core_num, cores_per_link, file_mapping_dict, network)`` Loads pre-computed modulation and GSNR data from files in ``data/pre_calc/{network}/``. Used for lookup-based SNR validation. ``get_slot_index(current_band, start_slot, engine_props)`` Converts band-relative slot index to absolute index. Handles L, C, S bands with configurable offsets. ``compute_response(mod_format, snr_props, spectrum_props, sdn_props)`` Validates that a modulation format can satisfy the bandwidth request. Returns ``True`` if valid, ``False`` otherwise. visualization/ (BETA) --------------------- :Purpose: Visualization plugin for SNR analysis plots :Status: **BETA** - API may change in future releases The visualization submodule provides a plugin that extends FUSION's core visualization system with SNR-specific plots: **Registered Metrics (7):** - ``snr`` - Signal-to-Noise Ratio (dB) - ``osnr`` - Optical SNR (dB) - ``q_factor`` - Quality factor (dB) - ``ber`` - Bit Error Rate - ``osnr_margin`` - Margin from required threshold (dB) - ``ase_noise`` - ASE noise power (dBm) - ``signal_power`` - Optical signal power (dBm) **Plot Types (3):** ``snr_vs_distance`` Shows SNR degradation along transmission distance with confidence intervals and threshold line. ``q_factor_plot`` Two-panel visualization: Q-factor vs distance (left) and BER vs SNR (right). ``osnr_margin_plot`` Bar chart showing OSNR margin for different configurations. Color-coded: green (safe, >3 dB), orange (warning, >1 dB), red (critical, <0 dB). See :ref:`snr-visualization` for usage details. Configuration ============= The SNR algorithm is configured via ``engine_props``: .. list-table:: Physical Layer Parameters :header-rows: 1 :widths: 30 15 55 * - Parameter - Default - Description * - ``bw_per_slot`` - 12.5e9 - Bandwidth per spectrum slot (Hz) * - ``input_power`` - 1e-3 - Launch power per channel (W) * - ``fiber_attenuation`` - 0.2 - Fiber loss (dB/km) * - ``fiber_dispersion`` - 16.7 - Chromatic dispersion (ps/nm/km) * - ``nonlinear_coefficient`` - 1.3e-3 - Kerr nonlinearity (1/W/km) * - ``edfa_noise_figure`` - 4.5 - EDFA noise figure (dB) * - ``span_length`` - 80.0 - Distance between amplifiers (km) .. list-table:: Multi-core Fiber Parameters :header-rows: 1 :widths: 30 15 55 * - Parameter - Default - Description * - ``cores_per_link`` - 1 - Number of cores (1 = single-core, 7 = MCF) * - ``xt_coefficient`` - -40 - Inter-core cross-talk coefficient (dB) **INI Configuration Example:** .. code-block:: ini [physical_layer] snr_algorithm = standard_snr fiber_attenuation = 0.2 fiber_dispersion = 16.7 edfa_noise_figure = 4.5 span_length = 80.0 [multicore] cores_per_link = 7 xt_coefficient = -40 Development Guide ================= Adding a New SNR Algorithm -------------------------- **Step 1: Create the algorithm file** .. code-block:: python # fusion/modules/snr/my_snr.py """My custom SNR measurement algorithm.""" from typing import Any from fusion.interfaces.snr import AbstractSNRMeasurer class MySNRMeasurer(AbstractSNRMeasurer): """ My custom SNR measurement algorithm. Implements [describe what makes it special]. """ @property def algorithm_name(self) -> str: return "my_snr" @property def supports_multicore(self) -> bool: return True # or False def calculate_snr( self, path: list[Any], spectrum_info: dict[str, Any] ) -> float: # YOUR SNR CALCULATION LOGIC total_snr_linear = 0.0 for i in range(len(path) - 1): link_snr = self.calculate_link_snr( path[i], path[i + 1], spectrum_info ) total_snr_linear += 10 ** (-link_snr / 10) return -10 * math.log10(total_snr_linear) def calculate_link_snr( self, source: Any, destination: Any, spectrum_info: dict[str, Any] ) -> float: # YOUR LINK SNR CALCULATION pass def calculate_crosstalk( self, path: list[Any], core_num: int, spectrum_info: dict[str, Any] ) -> float: # YOUR CROSSTALK CALCULATION pass def calculate_nonlinear_noise( self, path: list[Any], spectrum_info: dict[str, Any] ) -> dict[str, float]: return {"sci": 0.0, "xci": 0.0, "xpm": 0.0, "fwm": 0.0} def get_required_snr_threshold(self, modulation: str, reach: float) -> float: thresholds = { "BPSK": 6.0, "QPSK": 9.0, "8-QAM": 12.0, "16-QAM": 15.0, "32-QAM": 18.0, "64-QAM": 21.0, } base = thresholds.get(modulation, 9.0) reach_penalty = 0.1 * (reach / 100) return base + reach_penalty def is_snr_acceptable( self, calculated_snr: float, required_snr: float, margin: float = 0.0 ) -> bool: return calculated_snr >= (required_snr + margin) def update_link_state( self, source: Any, destination: Any, spectrum_info: dict[str, Any] ) -> None: pass # Override if algorithm maintains state def get_metrics(self) -> dict[str, Any]: return {"algorithm": self.algorithm_name} **Step 2: Register in registry.py** .. code-block:: python from .my_snr import MySNRMeasurer # In SNRRegistry._register_default_algorithms() self.register("my_snr", MySNRMeasurer) **Step 3: Export in __init__.py** .. code-block:: python from .my_snr import MySNRMeasurer __all__ = [ # ... existing exports "MySNRMeasurer", ] **Step 4: Add tests** Create ``tests/test_my_snr.py`` following the AAA pattern. Multi-core Fiber Support ------------------------ When implementing multi-core support: 1. Set ``supports_multicore = True`` 2. Handle ``core_num`` in ``spectrum_info`` 3. Implement ``calculate_crosstalk()`` properly 4. Consider adjacent core interference .. code-block:: python def calculate_crosstalk(self, path, core_num, spectrum_info): """Calculate inter-core cross-talk.""" # Determine number of adjacent cores (7-core hex: center=6, edge=3) if core_num == 0: # Center core num_adjacent = 6 else: num_adjacent = 3 # Cross-talk power xt_linear = 10 ** (self.xt_coefficient / 10) xt_power = xt_linear * num_adjacent * self.signal_power return xt_power Testing ======= :Test Location: ``fusion/modules/snr/tests/`` :Run Tests: ``pytest fusion/modules/snr/tests/ -v`` **Test Files:** - ``test_snr.py``: Core algorithm tests (SNR calculation, noise components) - ``test_registry.py``: Registry functionality (registration, creation) - ``test_utils.py``: Utility function tests (file loading, slot indexing) **Example Test:** .. code-block:: python def test_snr_decreases_with_distance(snr_measurer, mock_topology): """Test that SNR decreases for longer paths.""" spectrum_info = {"start_slot": 10, "end_slot": 18, "core_num": 0, "band": "c"} short_path = [0, 1] long_path = [0, 1, 2, 3, 4] snr_short = snr_measurer.calculate_snr(short_path, spectrum_info) snr_long = snr_measurer.calculate_snr(long_path, spectrum_info) assert snr_short > snr_long # Longer path = more noise = lower SNR Troubleshooting =============== **Issue: SNR always returns very low values** :Symptom: All paths fail SNR validation :Cause: Input power may be too low or fiber parameters incorrect :Solution: Check ``input_power`` (should be ~1 mW) and ``fiber_attenuation`` **Issue: Cross-talk not being calculated** :Symptom: Multi-core allocations show no cross-talk penalty :Cause: ``cores_per_link`` may be set to 1 (single-core mode) :Solution: Set ``cores_per_link = 7`` for multi-core fiber **Issue: Modulation always fails validation** :Symptom: High-order modulations (64-QAM) always rejected :Cause: Path length exceeds modulation reach :Solution: Use lower-order modulation for long paths, or check reach penalty Related Documentation ===================== - :ref:`modules-directory` - Overview of all FUSION modules - :ref:`routing-module` - Routing algorithms (path selection) - :ref:`interfaces-module` - ``AbstractSNRMeasurer`` interface .. seealso:: - `Optical SNR Fundamentals `_ - `GN Model for Nonlinear Noise `_ .. _snr-visualization: Visualization Submodule (BETA) ============================== .. note:: **Status: BETA** The visualization submodule is in BETA and actively being developed. The API may evolve in future releases. **Usage:** .. code-block:: python from fusion.visualization.plugins import get_global_registry # Load the plugin registry = get_global_registry() registry.discover_plugins() registry.load_plugin("snr") # Generate plots via standard API from fusion.visualization.application.use_cases.generate_plot import generate_plot # SNR vs distance plot result = generate_plot( config_path="my_experiment.yml", plot_type="snr_vs_distance", output_path="plots/snr_distance.png", ) # OSNR margin analysis result = generate_plot( config_path="my_experiment.yml", plot_type="osnr_margin_plot", output_path="plots/osnr_margin.png", )