Files
Python-Shannon/views/theory.py
Poidevin, Antoine (ITOP CM) - AF 6a4ccc3376
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 3m1s
feat: add interactive exploration of Shannon's capacity formula with Plotly graphs
- Implemented bandwidth sensitivity and power sensitivity plots.
- Created a contour map for bit rate multiplying factors.
- Added input parameters for C/N and bandwidth with validation.
- Displayed computed results and sensitivity analysis metrics.
- Integrated interactive graphs for user exploration.
- Included background information section for user guidance.
2026-02-20 10:33:09 +01:00

270 lines
8.3 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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<br><sub>C/N₀ = {c_n0:.1f} MHz</sub>",
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<br>"
f"<sub>Reference: C/N = {cnr_linear:.1f} [Linear]</sub>",
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<br><sub>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</sub>",
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="") 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])