feat: add interactive exploration of Shannon's capacity formula with Plotly graphs
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 3m1s

- 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.
This commit is contained in:
Poidevin, Antoine (ITOP CM) - AF
2026-02-20 10:33:09 +01:00
parent beda405953
commit 6a4ccc3376
38 changed files with 4319 additions and 11161 deletions

269
views/theory.py Normal file
View File

@@ -0,0 +1,269 @@
"""
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])