""" Page 1: Shannon's Equation — Theoretical Exploration Interactive exploration of the Shannon capacity formula with Plotly graphs. """ import streamlit as st import numpy as np import plotly.graph_objects as go from math import log from core.calculations import ( combine_cnr, shannon_capacity, shannon_points, br_multiplier, fmt_br, ) from core.help_texts import THEORY_HELP def _make_bw_sensitivity_plot(cnr_nyq: float, bw_nyq: float, c_n0: float) -> go.Figure: """Bandwidth sensitivity at constant power.""" n = 40 bw = np.zeros(n) br = np.zeros(n) cnr = np.zeros(n) cnr[0] = cnr_nyq + 10 * log(8, 10) bw[0] = bw_nyq / 8 br[0] = shannon_capacity(bw[0], cnr[0]) for i in range(1, n): bw[i] = bw[i - 1] * 2 ** (1 / 6) cnr[i] = cnr[i - 1] - 10 * log(bw[i] / bw[i - 1], 10) br[i] = shannon_capacity(bw[i], cnr[i]) fig = go.Figure() fig.add_trace(go.Scatter( x=bw, y=br, mode="lines", name="Shannon Capacity", line=dict(color="#4FC3F7", width=3), )) # Mark reference point ref_br = shannon_capacity(bw_nyq, cnr_nyq) fig.add_trace(go.Scatter( x=[bw_nyq], y=[ref_br], mode="markers+text", name=f"Reference: {bw_nyq:.1f} MHz, {ref_br:.1f} Mbps", marker=dict(size=12, color="#FF7043", symbol="diamond"), text=[f"{ref_br:.1f} Mbps"], textposition="top center", )) fig.update_layout( title=f"Theoretical Bit Rate at Constant Power
C/N₀ = {c_n0:.1f} MHz", xaxis_title="Bandwidth [MHz]", yaxis_title="Bit Rate [Mbps]", template="plotly_dark", height=500, showlegend=True, legend=dict(yanchor="bottom", y=0.02, xanchor="right", x=0.98), ) return fig def _make_power_sensitivity_plot(cnr_nyq: float, bw_nyq: float, cnr_linear: float) -> go.Figure: """Power sensitivity at constant bandwidth.""" n = 40 p_mul = np.zeros(n) br = np.zeros(n) cnr = np.zeros(n) p_mul[0] = 1 / 8 cnr[0] = cnr_nyq - 10 * log(8, 10) br[0] = shannon_capacity(bw_nyq, cnr[0]) for i in range(1, n): p_mul[i] = p_mul[i - 1] * 2 ** (1 / 6) cnr[i] = cnr[i - 1] + 10 * log(2 ** (1 / 6), 10) br[i] = shannon_capacity(bw_nyq, cnr[i]) fig = go.Figure() fig.add_trace(go.Scatter( x=p_mul, y=br, mode="lines", name="Shannon Capacity", line=dict(color="#81C784", width=3), )) # Reference point (multiplier = 1) ref_br = shannon_capacity(bw_nyq, cnr_nyq) fig.add_trace(go.Scatter( x=[1.0], y=[ref_br], mode="markers+text", name=f"Reference: 1x, {ref_br:.1f} Mbps", marker=dict(size=12, color="#FF7043", symbol="diamond"), text=[f"{ref_br:.1f} Mbps"], textposition="top center", )) fig.update_layout( title=f"Theoretical Bit Rate at Constant Bandwidth: {bw_nyq:.1f} MHz
" f"Reference: C/N = {cnr_linear:.1f} [Linear]", xaxis_title="Power Multiplying Factor", yaxis_title="Bit Rate [Mbps]", template="plotly_dark", height=500, showlegend=True, legend=dict(yanchor="bottom", y=0.02, xanchor="right", x=0.98), ) return fig def _make_br_factor_map(cnr_nyq: float, bw_nyq: float, c_n0: float, br_bw: float) -> go.Figure: """Contour map of BR multiplying factors.""" n = 41 bw_mul = np.zeros((n, n)) p_mul = np.zeros((n, n)) br_mul = np.zeros((n, n)) for i in range(n): for j in range(n): bw_mul[i, j] = (i + 1) / 8 p_mul[i, j] = (j + 1) / 8 br_mul[i, j] = br_multiplier(bw_mul[i, j], p_mul[i, j], cnr_nyq) fig = go.Figure(data=go.Contour( z=br_mul, x=bw_mul[:, 0], y=p_mul[0, :], colorscale="Viridis", contours=dict(showlabels=True, labelfont=dict(size=10, color="white")), colorbar=dict(title="BR Factor"), )) fig.update_layout( title=f"Bit Rate Multiplying Factor
Ref: C/N = {cnr_nyq:.1f} dB, " f"BW = {bw_nyq:.1f} MHz, C/N₀ = {c_n0:.1f} MHz, " f"BR = {br_bw:.1f} Mbps", xaxis_title="Bandwidth Multiplying Factor", yaxis_title="Power Multiplying Factor", template="plotly_dark", height=550, ) return fig def render(): """Render the Theoretical Exploration page.""" # ── Header ── col_img, col_title = st.columns([1, 3]) with col_img: st.image("Shannon.png", width=200) with col_title: st.markdown("# Shannon's Equation for Dummies") st.markdown( "Exploration of Claude Shannon's channel capacity theorem — " "the fundamental limit of digital communications." ) st.link_button("Wiki: Claude Shannon", "https://en.wikipedia.org/wiki/Claude_Shannon") st.divider() # ── Input Parameters ── st.markdown("### Input Parameters") col_in1, col_in2 = st.columns(2) with col_in1: cnr_input = st.text_input( "Reference C/N [dB]", value="12", help=THEORY_HELP["cnr"], ) with col_in2: bw_input = st.number_input( "Reference BW [MHz]", value=36.0, min_value=0.1, step=1.0, help=THEORY_HELP["bw"], ) # Parse CNR (supports comma-separated combinations) try: cnr_values = [float(v.strip()) for v in cnr_input.split(",")] cnr_nyq = combine_cnr(*cnr_values) except (ValueError, ZeroDivisionError): st.error("Invalid C/N values. Use comma-separated numbers (e.g., '12' or '12, 15').") return # ── Computed Results ── cnr_linear, br_inf, c_n0, br_bw = shannon_points(bw_input, cnr_nyq) br_unit = c_n0 # Spectral efficiency = 1 st.markdown("### Results") st.info(THEORY_HELP["c_n0"], icon=":material/info:") if st.checkbox("Show C/N₀ explanation", value=False) else None m1, m2, m3, m4 = st.columns(4) m1.metric("C/N₀", f"{c_n0:.1f} MHz", help=THEORY_HELP["c_n0"]) m2.metric("BR at ∞ BW", fmt_br(br_inf), help=THEORY_HELP["br_inf"]) m3.metric("BR at SpEff=1", fmt_br(br_unit), help=THEORY_HELP["br_unit"]) m4.metric("BR at Ref BW", fmt_br(br_bw), help=THEORY_HELP["br_bw"]) st.metric( "C/N Ratio", f"{cnr_nyq:.1f} dB · {cnr_linear:.1f} linear", help=THEORY_HELP["cnr_lin"], ) st.divider() # ── Sensitivity Analysis ── st.markdown("### Sensitivity Analysis") col_s1, col_s2, col_s3 = st.columns(3) with col_s1: bw_mul_val = st.number_input( "BW Increase Factor", value=1.0, min_value=0.01, step=0.25, help=THEORY_HELP["bw_mul"], ) with col_s2: p_mul_val = st.number_input( "Power Increase Factor", value=2.0, min_value=0.01, step=0.25, help=THEORY_HELP["p_mul"], ) with col_s3: br_mul_val = br_multiplier(bw_mul_val, p_mul_val, cnr_nyq) st.metric( "Bit Rate Factor", f"{br_mul_val:.3f}", delta=f"{(br_mul_val - 1) * 100:+.1f}%", help=THEORY_HELP["br_mul"], ) st.divider() # ── Graphs ── st.markdown("### Interactive Graphs") tab_bw, tab_pow, tab_map = st.tabs([ "Bandwidth Sensitivity", "Power Sensitivity", "BR Factor Map", ]) with tab_bw: st.plotly_chart( _make_bw_sensitivity_plot(cnr_nyq, bw_input, c_n0), width="stretch", ) with tab_pow: st.plotly_chart( _make_power_sensitivity_plot(cnr_nyq, bw_input, cnr_linear), width="stretch", ) with tab_map: st.plotly_chart( _make_br_factor_map(cnr_nyq, bw_input, c_n0, br_bw), width="stretch", ) # ── Help Section ── with st.expander("Background Information"): help_topic = st.selectbox( "Choose a topic:", options=["shannon", "advanced", "help"], format_func=lambda x: { "shannon": "Shannon's Equation", "advanced": "Advanced (AWGN Model)", "help": "How to use this tool", }[x], ) st.markdown(THEORY_HELP[help_topic])