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
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:
269
views/theory.py
Normal file
269
views/theory.py
Normal 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])
|
||||
Reference in New Issue
Block a user