euler314's picture
Update app.py
f84f14b verified
raw
history blame
27.8 kB
import streamlit as st
import subprocess
import os
import json
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from PIL import Image
import time
import io
# Set page config with wider layout
st.set_page_config(
page_title="Eigenvalue Analysis Dashboard",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded"
)
# Apply custom CSS for a dashboard-like appearance
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
color: #1E88E5;
text-align: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f0f0f0;
}
.dashboard-container {
background-color: #f9f9f9;
padding: 1.5rem;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 1.5rem;
}
.panel-header {
font-size: 1.3rem;
font-weight: bold;
margin-bottom: 1rem;
color: #424242;
border-left: 4px solid #1E88E5;
padding-left: 10px;
}
.stats-card {
background-color: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
text-align: center;
}
.stats-value {
font-size: 1.8rem;
font-weight: bold;
color: #1E88E5;
}
.stats-label {
font-size: 0.9rem;
color: #616161;
margin-top: 0.3rem;
}
</style>
""", unsafe_allow_html=True)
# Dashboard Header
st.markdown('<h1 class="main-header">Eigenvalue Analysis Dashboard</h1>', unsafe_allow_html=True)
# Create output directory in the current working directory
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "output")
os.makedirs(output_dir, exist_ok=True)
# Compile the C++ code at runtime
cpp_file = os.path.join(current_dir, "app.cpp")
executable = os.path.join(current_dir, "eigen_analysis")
# Two-column layout for the dashboard
left_column, right_column = st.columns([1, 3])
with left_column:
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
st.markdown('<div class="panel-header">Control Panel</div>', unsafe_allow_html=True)
# Check if cpp file exists and compile if necessary
if not os.path.exists(cpp_file):
st.error(f"C++ source file not found at: {cpp_file}")
st.stop()
# Compile the C++ code with the right OpenCV libraries
if not os.path.exists(executable) or st.button("Recompile C++ Code"):
with st.spinner("Compiling C++ code..."):
compile_commands = [
f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv4` -std=c++11",
f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv` -std=c++11",
f"g++ -o {executable} {cpp_file} -I/usr/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11"
]
compiled = False
for cmd in compile_commands:
compile_result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True
)
if compile_result.returncode == 0:
compiled = True
break
if not compiled:
st.error("All compilation attempts failed. Please check the system requirements.")
st.stop()
# Make sure the executable is executable
os.chmod(executable, 0o755)
st.success("C++ code compiled successfully")
# Parameter inputs with defaults and validation
st.markdown("### Matrix Parameters")
n = st.number_input("Sample size (n)", min_value=5, max_value=1000, value=100, step=5, help="Number of samples")
p = st.number_input("Dimension (p)", min_value=5, max_value=1000, value=50, step=5, help="Dimensionality")
a = st.number_input("Value for a", min_value=1.1, max_value=10.0, value=2.0, step=0.1, help="Parameter a > 1")
# Automatically calculate y = p/n (as requested)
y = p/n
st.info(f"Value for y = p/n: {y:.4f}")
st.markdown("### Calculation Controls")
fineness = st.slider(
"Beta points",
min_value=20,
max_value=500,
value=100,
step=10,
help="Number of points to calculate along the β axis (0 to 1)"
)
with st.expander("Advanced Settings"):
# Add controls for theoretical calculation precision
theory_grid_points = st.slider(
"Theoretical grid points",
min_value=100,
max_value=1000,
value=200,
step=50,
help="Number of points in initial grid search for theoretical calculations"
)
theory_tolerance = st.number_input(
"Theoretical tolerance",
min_value=1e-12,
max_value=1e-6,
value=1e-10,
format="%.1e",
help="Convergence tolerance for golden section search"
)
# Generate button
generate_button = st.button("Generate Analysis", type="primary", use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
# About section
with st.expander("About Eigenvalue Analysis"):
st.markdown("""
## Theory
This application visualizes the relationship between empirical and theoretical eigenvalues for matrices with specific properties.
The analysis examines:
- **Empirical Max/Min Eigenvalues**: The maximum and minimum eigenvalues calculated from the generated matrices
- **Theoretical Max/Min Functions**: The theoretical bounds derived from mathematical analysis
### Key Parameters
- **n**: Sample size
- **p**: Dimension
- **a**: Value > 1 that affects the distribution of eigenvalues
- **y**: Value calculated as p/n that affects scaling
### Calculation Controls
- **Beta points**: Number of points calculated along the β range (0 to 1)
- **Theoretical grid points**: Number of points in initial grid search for finding theoretical max/min
- **Theoretical tolerance**: Convergence tolerance for golden section search algorithm
### Mathematical Formulas
Max Function:
max{k ∈ (0,∞)} [yβ(a-1)k + (ak+1)((y-1)k-1)]/[(ak+1)(k²+k)]
Min Function:
min{t ∈ (-1/a,0)} [yβ(a-1)t + (at+1)((y-1)t-1)]/[(at+1)(t²+t)]
""")
with right_column:
# Main visualization area
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
st.markdown('<div class="panel-header">Eigenvalue Analysis Visualization</div>', unsafe_allow_html=True)
# Container for the analysis results
results_container = st.container()
# Process when generate button is clicked
if generate_button:
with results_container:
# Show progress
progress_container = st.container()
with progress_container:
progress_bar = st.progress(0)
status_text = st.empty()
try:
# Run the C++ executable with the parameters in JSON output mode
data_file = os.path.join(output_dir, "eigenvalue_data.json")
# Delete previous output if exists
if os.path.exists(data_file):
os.remove(data_file)
# Execute the C++ program
cmd = [
executable,
str(n),
str(p),
str(a),
str(y),
str(fineness),
str(theory_grid_points),
str(theory_tolerance),
data_file
]
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Show output in a status area
status_text.text("Starting calculations...")
last_progress = 0
while process.poll() is None:
output = process.stdout.readline()
if output:
if output.startswith("PROGRESS:"):
try:
# Update progress bar
progress_value = float(output.split(":")[1].strip())
progress_bar.progress(progress_value)
last_progress = progress_value
status_text.text(f"Calculating... {int(progress_value * 100)}% complete")
except:
pass
else:
status_text.text(output.strip())
time.sleep(0.1)
return_code = process.poll()
if return_code != 0:
error = process.stderr.read()
st.error(f"Error executing the analysis: {error}")
else:
progress_bar.progress(1.0)
status_text.text("Calculations complete! Generating visualization...")
# Load the results from the JSON file
with open(data_file, 'r') as f:
data = json.load(f)
# Extract data
beta_values = np.array(data['beta_values'])
max_eigenvalues = np.array(data['max_eigenvalues'])
min_eigenvalues = np.array(data['min_eigenvalues'])
theoretical_max = np.array(data['theoretical_max'])
theoretical_min = np.array(data['theoretical_min'])
# Create an interactive plot using Plotly
fig = go.Figure()
# Add traces for each line
fig.add_trace(go.Scatter(
x=beta_values,
y=max_eigenvalues,
mode='lines+markers',
name='Empirical Max Eigenvalue',
line=dict(color='rgb(220, 60, 60)', width=3),
marker=dict(
symbol='circle',
size=8,
color='rgb(220, 60, 60)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=min_eigenvalues,
mode='lines+markers',
name='Empirical Min Eigenvalue',
line=dict(color='rgb(60, 60, 220)', width=3),
marker=dict(
symbol='circle',
size=8,
color='rgb(60, 60, 220)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_max,
mode='lines+markers',
name='Theoretical Max Function',
line=dict(color='rgb(30, 180, 30)', width=3),
marker=dict(
symbol='diamond',
size=8,
color='rgb(30, 180, 30)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_min,
mode='lines+markers',
name='Theoretical Min Function',
line=dict(color='rgb(180, 30, 180)', width=3),
marker=dict(
symbol='diamond',
size=8,
color='rgb(180, 30, 180)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
))
# Configure layout for better appearance
fig.update_layout(
title={
'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
'font': {'size': 24, 'color': '#1E88E5'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': 'β Parameter',
'titlefont': {'size': 18, 'color': '#424242'},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
yaxis={
'title': 'Eigenvalues',
'titlefont': {'size': 18, 'color': '#424242'},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
plot_bgcolor='rgba(240, 240, 240, 0.8)',
paper_bgcolor='rgba(249, 249, 249, 0.8)',
hovermode='closest',
legend={
'font': {'size': 14},
'bgcolor': 'rgba(255, 255, 255, 0.9)',
'bordercolor': 'rgba(200, 200, 200, 0.5)',
'borderwidth': 1
},
margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
height=600,
annotations=[
{
'text': f"Max Function: max{{k ∈ (0,∞)}} [yβ(a-1)k + (ak+1)((y-1)k-1)]/[(ak+1)(k²+k)]",
'xref': 'paper', 'yref': 'paper',
'x': 0.02, 'y': 0.02,
'showarrow': False,
'font': {'size': 12, 'color': 'rgb(30, 180, 30)'},
'bgcolor': 'rgba(255, 255, 255, 0.9)',
'bordercolor': 'rgb(30, 180, 30)',
'borderwidth': 1,
'borderpad': 4
},
{
'text': f"Min Function: min{{t ∈ (-1/a,0)}} [yβ(a-1)t + (at+1)((y-1)t-1)]/[(at+1)(t²+t)]",
'xref': 'paper', 'yref': 'paper',
'x': 0.55, 'y': 0.02,
'showarrow': False,
'font': {'size': 12, 'color': 'rgb(180, 30, 180)'},
'bgcolor': 'rgba(255, 255, 255, 0.9)',
'bordercolor': 'rgb(180, 30, 180)',
'borderwidth': 1,
'borderpad': 4
}
]
)
# Add custom modebar buttons
fig.update_layout(
modebar_add=[
'drawline', 'drawopenpath', 'drawclosedpath',
'drawcircle', 'drawrect', 'eraseshape'
],
modebar_remove=['lasso2d', 'select2d'],
dragmode='zoom'
)
# Clear progress container
progress_container.empty()
# Display the interactive plot in Streamlit
st.plotly_chart(fig, use_container_width=True)
# Generate static image for download
output_file = os.path.join(output_dir, "eigenvalue_analysis.png")
fig.write_image(output_file, scale=2)
# Provide download button
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
with open(output_file, "rb") as file:
btn = st.download_button(
label="Download Plot",
data=file,
file_name=f"eigenvalue_analysis_n{n}_p{p}_a{a}_y{y:.4f}.png",
mime="image/png",
use_container_width=True
)
# Add statistics section with cards
st.markdown("### Results Summary")
# Calculate key statistics
emp_max = max(max_eigenvalues)
emp_min = min(min_eigenvalues)
theo_max = max(theoretical_max)
theo_min = min(theoretical_min)
max_diff = abs(emp_max - theo_max)
min_diff = abs(emp_min - theo_min)
# Display statistics in a card layout
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{emp_max:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Empirical Maximum</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
with col2:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{emp_min:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Empirical Minimum</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
with col3:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{theo_max:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Theoretical Maximum</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
with col4:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{theo_min:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Theoretical Minimum</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{max_diff:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Max Difference</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
with col2:
st.markdown('<div class="stats-card">', unsafe_allow_html=True)
st.markdown(f'<div class="stats-value">{min_diff:.4f}</div>', unsafe_allow_html=True)
st.markdown('<div class="stats-label">Min Difference</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Add calculation settings
with st.expander("Calculation Details"):
st.markdown(f"""
- **Matrix Dimensions**: {n} × {p}
- **Parameter a**: {a}
- **Parameter y (p/n)**: {y:.4f}
- **Beta points**: {fineness}
- **Theoretical grid points**: {theory_grid_points}
- **Theoretical tolerance**: {theory_tolerance:.1e}
""")
except Exception as e:
st.error(f"An error occurred: {str(e)}")
else:
# Try to load existing data if available
data_file = os.path.join(output_dir, "eigenvalue_data.json")
if os.path.exists(data_file):
try:
with open(data_file, 'r') as f:
data = json.load(f)
# Extract data
beta_values = np.array(data['beta_values'])
max_eigenvalues = np.array(data['max_eigenvalues'])
min_eigenvalues = np.array(data['min_eigenvalues'])
theoretical_max = np.array(data['theoretical_max'])
theoretical_min = np.array(data['theoretical_min'])
# Create an interactive plot using Plotly
fig = go.Figure()
# Add traces for each line
fig.add_trace(go.Scatter(
x=beta_values,
y=max_eigenvalues,
mode='lines+markers',
name='Empirical Max Eigenvalue',
line=dict(color='rgb(220, 60, 60)', width=3),
marker=dict(
symbol='circle',
size=8,
color='rgb(220, 60, 60)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=min_eigenvalues,
mode='lines+markers',
name='Empirical Min Eigenvalue',
line=dict(color='rgb(60, 60, 220)', width=3),
marker=dict(
symbol='circle',
size=8,
color='rgb(60, 60, 220)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_max,
mode='lines+markers',
name='Theoretical Max Function',
line=dict(color='rgb(30, 180, 30)', width=3),
marker=dict(
symbol='diamond',
size=8,
color='rgb(30, 180, 30)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_min,
mode='lines+markers',
name='Theoretical Min Function',
line=dict(color='rgb(180, 30, 180)', width=3),
marker=dict(
symbol='diamond',
size=8,
color='rgb(180, 30, 180)',
line=dict(color='white', width=1)
),
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
))
# Configure layout for better appearance
fig.update_layout(
title={
'text': f'Eigenvalue Analysis (Previous Result)',
'font': {'size': 24, 'color': '#1E88E5'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': 'β Parameter',
'titlefont': {'size': 18, 'color': '#424242'},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
yaxis={
'title': 'Eigenvalues',
'titlefont': {'size': 18, 'color': '#424242'},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
plot_bgcolor='rgba(240, 240, 240, 0.8)',
paper_bgcolor='rgba(249, 249, 249, 0.8)',
hovermode='closest',
legend={
'font': {'size': 14},
'bgcolor': 'rgba(255, 255, 255, 0.9)',
'bordercolor': 'rgba(200, 200, 200, 0.5)',
'borderwidth': 1
},
margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
height=600
)
# Display the interactive plot in Streamlit
st.plotly_chart(fig, use_container_width=True)
st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
except Exception as e:
st.info("👈 Set parameters and click 'Generate Analysis' to create a visualization.")
else:
# Show placeholder
st.info("👈 Set parameters and click 'Generate Analysis' to create a visualization.")
st.markdown('</div>', unsafe_allow_html=True)