', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
# Container for the analysis results
eig_results_container = st.container()
# Process when generate button is clicked
if eig_generate_button:
with eig_results_container:
# Show progress
progress_container = st.container()
with progress_container:
progress_bar = st.progress(0)
status_text = st.empty()
try:
# Create data file path
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)
# Build command for eigenvalue analysis with the proper arguments
cmd = [
executable,
"eigenvalues", # Mode argument
str(n),
str(p),
str(a),
str(y),
str(fineness),
str(theory_grid_points),
str(theory_tolerance),
data_file
]
# Run the command
status_text.text("Running eigenvalue analysis...")
if debug_mode:
success, stdout, stderr = run_command(cmd, True, timeout=timeout_seconds)
# Process stdout for progress updates
if success:
progress_bar.progress(1.0)
else:
# Start the process with pipe for stdout to read progress
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
# Track progress from stdout
success = True
stdout_lines = []
start_time = time.time()
while True:
# Check for timeout
if time.time() - start_time > timeout_seconds:
process.kill()
status_text.error(f"Computation timed out after {timeout_seconds} seconds")
success = False
break
# Try to read a line (non-blocking)
line = process.stdout.readline()
if not line and process.poll() is not None:
break
if line:
stdout_lines.append(line)
if line.startswith("PROGRESS:"):
try:
# Update progress bar
progress_value = float(line.split(":")[1].strip())
progress_bar.progress(progress_value)
status_text.text(f"Calculating... {int(progress_value * 100)}% complete")
except:
pass
elif line:
status_text.text(line.strip())
# Get the return code and stderr
returncode = process.poll()
stderr = process.stderr.read()
if returncode != 0:
success = False
st.error(f"Error executing the analysis: {stderr}")
with st.expander("Error Details"):
st.code(stderr)
if success:
progress_bar.progress(1.0)
status_text.text("Analysis complete! Generating visualization...")
# Check if the output file was created
if not os.path.exists(data_file):
st.error(f"Output file not created: {data_file}")
st.stop()
try:
# Load the results from the JSON file
with open(data_file, 'r') as f:
data = json.load(f)
# Process data - convert string values to numeric
beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
theoretical_min = np.array([safe_convert_to_numeric(x) for x in 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=color_max, width=3),
marker=dict(
symbol='circle',
size=8,
color=color_max,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Empirical Max'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=min_eigenvalues,
mode='lines+markers',
name='Empirical Min Eigenvalue',
line=dict(color=color_min, width=3),
marker=dict(
symbol='circle',
size=8,
color=color_min,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Empirical Min'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_max,
mode='lines+markers',
name='Theoretical Max',
line=dict(color=color_theory_max, width=3),
marker=dict(
symbol='diamond',
size=8,
color=color_theory_max,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Theoretical Max'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_min,
mode='lines+markers',
name='Theoretical Min',
line=dict(color=color_theory_min, width=3),
marker=dict(
symbol='diamond',
size=8,
color=color_theory_min,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Theoretical Min'
))
# 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': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
yaxis={
'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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,
)
# 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)
# Display statistics in a cleaner way
st.markdown('
', unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}")
with col2:
st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}")
with col3:
st.metric("Max Theoretical", f"{theoretical_max.max():.4f}")
with col4:
st.metric("Min Theoretical", f"{theoretical_min.min():.4f}")
st.markdown('
', unsafe_allow_html=True)
except json.JSONDecodeError as e:
st.error(f"Error parsing JSON results: {str(e)}")
if os.path.exists(data_file):
with open(data_file, 'r') as f:
content = f.read()
st.code(content[:1000] + "..." if len(content) > 1000 else content)
except Exception as e:
st.error(f"An error occurred: {str(e)}")
if debug_mode:
st.exception(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)
# Process data - convert string values to numeric
beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
theoretical_min = np.array([safe_convert_to_numeric(x) for x in 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=color_max, width=3),
marker=dict(
symbol='circle',
size=8,
color=color_max,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Empirical Max'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=min_eigenvalues,
mode='lines+markers',
name='Empirical Min Eigenvalue',
line=dict(color=color_min, width=3),
marker=dict(
symbol='circle',
size=8,
color=color_min,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Empirical Min'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_max,
mode='lines+markers',
name='Theoretical Max',
line=dict(color=color_theory_max, width=3),
marker=dict(
symbol='diamond',
size=8,
color=color_theory_max,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Theoretical Max'
))
fig.add_trace(go.Scatter(
x=beta_values,
y=theoretical_min,
mode='lines+markers',
name='Theoretical Min',
line=dict(color=color_theory_min, width=3),
marker=dict(
symbol='diamond',
size=8,
color=color_theory_min,
line=dict(color='white', width=1)
),
hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}
Theoretical Min'
))
# Configure layout for better appearance
fig.update_layout(
title={
'text': f'Eigenvalue Analysis (Previous Result)',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
yaxis={
'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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 Eigenvalue Analysis' to create a visualization.")
else:
# Show placeholder
st.info("π Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
st.markdown('
', unsafe_allow_html=True)
# Tab 2: Im(s) vs z Analysis with SymPy
with tab2:
# Two-column layout for the dashboard
left_column, right_column = st.columns([1, 3])
with left_column:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
# Container for the analysis results
cubic_results_container = st.container()
# Process when generate button is clicked
if cubic_generate_button:
with cubic_results_container:
# Show progress
progress_container = st.container()
with progress_container:
progress_bar = st.progress(0)
status_text = st.empty()
status_text.text("Starting cubic equation calculations with SymPy...")
try:
# Create data file path
data_file = os.path.join(output_dir, "cubic_data.json")
# Run the Im(s) vs z analysis using Python SymPy with high precision
start_time = time.time()
# Define progress callback for updating the progress bar
def update_progress(progress):
progress_bar.progress(progress)
status_text.text(f"Calculating with SymPy... {int(progress * 100)}% complete")
# Run the analysis with progress updates
result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
end_time = time.time()
# Format the data for saving
save_data = {
'z_values': result['z_values'],
'ims_values1': result['ims_values1'],
'ims_values2': result['ims_values2'],
'ims_values3': result['ims_values3'],
'real_values1': result['real_values1'],
'real_values2': result['real_values2'],
'real_values3': result['real_values3']
}
# Save results to JSON
save_as_json(save_data, data_file)
status_text.text("SymPy calculations complete! Generating visualization...")
# Extract data
z_values = result['z_values']
ims_values1 = result['ims_values1']
ims_values2 = result['ims_values2']
ims_values3 = result['ims_values3']
real_values1 = result['real_values1']
real_values2 = result['real_values2']
real_values3 = result['real_values3']
# Find the maximum value for consistent y-axis scaling
max_im_value = max(np.max(ims_values1), np.max(ims_values2), np.max(ims_values3))
# Create tabs for imaginary and real parts
im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"])
# Tab for imaginary parts
with im_tab:
# Create an interactive plot for imaginary parts with improved layout
im_fig = go.Figure()
# Add traces for each root's imaginary part
im_fig.add_trace(go.Scatter(
x=z_values,
y=ims_values1,
mode='lines',
name='Im(sβ)',
line=dict(color=color_max, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 1'
))
im_fig.add_trace(go.Scatter(
x=z_values,
y=ims_values2,
mode='lines',
name='Im(sβ)',
line=dict(color=color_min, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 2'
))
im_fig.add_trace(go.Scatter(
x=z_values,
y=ims_values3,
mode='lines',
name='Im(sβ)',
line=dict(color=color_theory_max, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 3'
))
# Configure layout for better appearance
im_fig.update_layout(
title={
'text': f'Im(s) vs z Analysis: a={cubic_a}, y={cubic_y}, Ξ²={cubic_beta}',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'type': 'log' # Use logarithmic scale for better visualization
},
yaxis={
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'range': [0, max_im_value * 1.1] # Set a fixed range with some padding
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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=500,
)
# Display the interactive plot in Streamlit
st.plotly_chart(im_fig, use_container_width=True)
# Tab for real parts
with real_tab:
# Find the min and max for symmetric y-axis range
real_min = min(np.min(real_values1), np.min(real_values2), np.min(real_values3))
real_max = max(np.max(real_values1), np.max(real_values2), np.max(real_values3))
y_range = max(abs(real_min), abs(real_max))
# Create an interactive plot for real parts with improved layout
real_fig = go.Figure()
# Add traces for each root's real part
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values1,
mode='lines',
name='Re(sβ)',
line=dict(color=color_max, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 1'
))
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values2,
mode='lines',
name='Re(sβ)',
line=dict(color=color_min, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 2'
))
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values3,
mode='lines',
name='Re(sβ)',
line=dict(color=color_theory_max, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 3'
))
# Add zero line for reference
real_fig.add_shape(
type="line",
x0=min(z_values),
y0=0,
x1=max(z_values),
y1=0,
line=dict(
color="black",
width=1,
dash="dash",
)
)
# Configure layout for better appearance
real_fig.update_layout(
title={
'text': f'Re(s) vs z Analysis: a={cubic_a}, y={cubic_y}, Ξ²={cubic_beta}',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'type': 'log' # Use logarithmic scale for better visualization
},
yaxis={
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'range': [-y_range * 1.1, y_range * 1.1] # Symmetric range with padding
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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=500
)
# Display the interactive plot in Streamlit
st.plotly_chart(real_fig, use_container_width=True)
# Tab for root pattern
with pattern_tab:
# Count different patterns
zero_count = 0
positive_count = 0
negative_count = 0
# Count points that match the pattern "one negative, one positive, one zero"
pattern_count = 0
all_zeros_count = 0
for i in range(len(z_values)):
# Count roots at this z value
zeros = 0
positives = 0
negatives = 0
# Handle NaN values
r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0
r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0
r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0
for r in [r1, r2, r3]:
if abs(r) < 1e-6:
zeros += 1
elif r > 0:
positives += 1
else:
negatives += 1
if zeros == 3:
all_zeros_count += 1
elif zeros == 1 and positives == 1 and negatives == 1:
pattern_count += 1
# Create a summary plot
st.markdown('
', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.metric("Points with pattern (1 neg, 1 pos, 1 zero)", f"{pattern_count}/{len(z_values)}")
with col2:
st.metric("Points with all zeros", f"{all_zeros_count}/{len(z_values)}")
st.markdown('
', unsafe_allow_html=True)
# Detailed pattern analysis plot
pattern_fig = go.Figure()
# Create colors for root types
colors_at_z = []
patterns_at_z = []
for i in range(len(z_values)):
# Count roots at this z value
zeros = 0
positives = 0
negatives = 0
# Handle NaN values
r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0
r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0
r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0
for r in [r1, r2, r3]:
if abs(r) < 1e-6:
zeros += 1
elif r > 0:
positives += 1
else:
negatives += 1
# Determine pattern color
# Determine pattern color
if zeros == 3:
colors_at_z.append('#4CAF50') # Green for all zeros
patterns_at_z.append('All zeros')
elif zeros == 1 and positives == 1 and negatives == 1:
colors_at_z.append('#2196F3') # Blue for desired pattern
patterns_at_z.append('1 neg, 1 pos, 1 zero')
else:
colors_at_z.append('#F44336') # Red for other patterns
patterns_at_z.append(f'{negatives} neg, {positives} pos, {zeros} zero')
# Plot root pattern indicator
pattern_fig.add_trace(go.Scatter(
x=z_values,
y=[1] * len(z_values), # Just a constant value for visualization
mode='markers',
marker=dict(
size=10,
color=colors_at_z,
symbol='circle'
),
hovertext=patterns_at_z,
hoverinfo='text+x',
name='Root Pattern'
))
# Configure layout
pattern_fig.update_layout(
title={
'text': 'Root Pattern Analysis',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'type': 'log'
},
yaxis={
'showticklabels': False,
'showgrid': False,
'zeroline': False,
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 0.8)',
height=300,
margin={'l': 40, 'r': 40, 't': 100, 'b': 40},
showlegend=False
)
# Add legend as annotations
pattern_fig.add_annotation(
x=0.01, y=0.95,
xref="paper", yref="paper",
text="Legend:",
showarrow=False,
font=dict(size=14)
)
pattern_fig.add_annotation(
x=0.07, y=0.85,
xref="paper", yref="paper",
text="β Ideal pattern (1 neg, 1 pos, 1 zero)",
showarrow=False,
font=dict(size=12, color="#2196F3")
)
pattern_fig.add_annotation(
x=0.07, y=0.75,
xref="paper", yref="paper",
text="β All zeros",
showarrow=False,
font=dict(size=12, color="#4CAF50")
)
pattern_fig.add_annotation(
x=0.07, y=0.65,
xref="paper", yref="paper",
text="β Other patterns",
showarrow=False,
font=dict(size=12, color="#F44336")
)
# Display the pattern figure
st.plotly_chart(pattern_fig, use_container_width=True)
# Root pattern explanation
st.markdown('
', unsafe_allow_html=True)
st.markdown("""
### Root Pattern Analysis
The cubic equation in this analysis should exhibit roots with the following pattern:
- One root with negative real part
- One root with positive real part
- One root with zero real part
Or in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values.
The Python implementation using SymPy's high-precision solver has been engineered to ensure this pattern is maintained, which is important for stability analysis.
When roots have imaginary parts, they occur in conjugate pairs, which explains why you may see matching Im(s) values in the Imaginary Parts tab.
The implementation uses SymPy's symbolic mathematics capabilities with extended precision to provide more accurate results than standard numerical methods.
""")
st.markdown('
', unsafe_allow_html=True)
# Additional visualization showing all three roots in the complex plane
st.markdown("### Roots in Complex Plane")
st.markdown("Below is a visualization of the three roots in the complex plane for a selected z value.")
# Slider for selecting z value to visualize
z_idx = st.slider(
"Select z index",
min_value=0,
max_value=len(z_values)-1,
value=len(z_values)//2,
help="Select a specific z value to visualize its roots in the complex plane"
)
# Selected z value and corresponding roots
selected_z = z_values[z_idx]
selected_roots = [
complex(real_values1[z_idx], ims_values1[z_idx]),
complex(real_values2[z_idx], ims_values2[z_idx]),
complex(real_values3[z_idx], -ims_values3[z_idx]) # Negative imaginary for the third root for visualization
]
# Create complex plane visualization
complex_fig = go.Figure()
# Add roots as points
complex_fig.add_trace(go.Scatter(
x=[root.real for root in selected_roots],
y=[root.imag for root in selected_roots],
mode='markers+text',
marker=dict(
size=12,
color=[color_max, color_min, color_theory_max],
symbol='circle',
line=dict(width=1, color='white')
),
text=['sβ', 'sβ', 'sβ'],
textposition="top center",
name='Roots'
))
# Add real and imaginary axes
complex_fig.add_shape(
type="line",
x0=-abs(max([r.real for r in selected_roots])) * 1.2,
y0=0,
x1=abs(max([r.real for r in selected_roots])) * 1.2,
y1=0,
line=dict(color="black", width=1, dash="solid")
)
complex_fig.add_shape(
type="line",
x0=0,
y0=-abs(max([r.imag for r in selected_roots])) * 1.2,
x1=0,
y1=abs(max([r.imag for r in selected_roots])) * 1.2,
line=dict(color="black", width=1, dash="solid")
)
# Add annotations for axes
complex_fig.add_annotation(
x=abs(max([r.real for r in selected_roots])) * 1.2,
y=0,
text="Re(s)",
showarrow=False,
xanchor="left"
)
complex_fig.add_annotation(
x=0,
y=abs(max([r.imag for r in selected_roots])) * 1.2,
text="Im(s)",
showarrow=False,
yanchor="bottom"
)
# Update layout for complex plane visualization
complex_fig.update_layout(
title=f"Roots in Complex Plane for z = {selected_z:.4f}",
xaxis=dict(
title="Real Part",
zeroline=False
),
yaxis=dict(
title="Imaginary Part",
zeroline=False,
scaleanchor="x",
scaleratio=1 # Equal aspect ratio
),
showlegend=False,
plot_bgcolor='rgba(250, 250, 250, 0.8)',
width=600,
height=500,
margin=dict(l=50, r=50, t=80, b=50),
annotations=[
dict(
text=f"Root 1: {selected_roots[0].real:.4f} + {selected_roots[0].imag:.4f}i",
x=0.02, y=0.98, xref="paper", yref="paper",
showarrow=False, font=dict(color=color_max)
),
dict(
text=f"Root 2: {selected_roots[1].real:.4f} + {selected_roots[1].imag:.4f}i",
x=0.02, y=0.94, xref="paper", yref="paper",
showarrow=False, font=dict(color=color_min)
),
dict(
text=f"Root 3: {selected_roots[2].real:.4f} + {selected_roots[2].imag:.4f}i",
x=0.02, y=0.90, xref="paper", yref="paper",
showarrow=False, font=dict(color=color_theory_max)
)
]
)
st.plotly_chart(complex_fig, use_container_width=True)
# Clear progress container
progress_container.empty()
# Display computation time
st.info(f"SymPy computation completed in {end_time - start_time:.2f} seconds")
except Exception as e:
st.error(f"An error occurred: {str(e)}")
st.exception(e)
else:
# Try to load existing data if available
data_file = os.path.join(output_dir, "cubic_data.json")
if os.path.exists(data_file):
try:
with open(data_file, 'r') as f:
data = json.load(f)
# Process data safely
z_values = np.array([safe_convert_to_numeric(x) for x in data['z_values']])
ims_values1 = np.array([safe_convert_to_numeric(x) for x in data['ims_values1']])
ims_values2 = np.array([safe_convert_to_numeric(x) for x in data['ims_values2']])
ims_values3 = np.array([safe_convert_to_numeric(x) for x in data['ims_values3']])
# Also extract real parts if available
real_values1 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(z_values))])
real_values2 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(z_values))])
real_values3 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(z_values))])
# Create tabs for previous results
prev_im_tab, prev_real_tab = st.tabs(["Previous Imaginary Parts", "Previous Real Parts"])
# Find the maximum value for consistent y-axis scaling
max_im_value = max(np.max(ims_values1), np.max(ims_values2), np.max(ims_values3))
# Tab for imaginary parts
with prev_im_tab:
# Show previous results with Imaginary parts
fig = go.Figure()
# Add traces for each root's imaginary part
fig.add_trace(go.Scatter(
x=z_values,
y=ims_values1,
mode='lines',
name='Im(sβ)',
line=dict(color=color_max, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 1'
))
fig.add_trace(go.Scatter(
x=z_values,
y=ims_values2,
mode='lines',
name='Im(sβ)',
line=dict(color=color_min, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 2'
))
fig.add_trace(go.Scatter(
x=z_values,
y=ims_values3,
mode='lines',
name='Im(sβ)',
line=dict(color=color_theory_max, width=3),
hovertemplate='z: %{x:.3f}
Im(sβ): %{y:.6f}
Root 3'
))
# Configure layout for better appearance
fig.update_layout(
title={
'text': 'Im(s) vs z Analysis (Previous Result)',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'type': 'log' # Use logarithmic scale for better visualization
},
yaxis={
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'range': [0, max_im_value * 1.1] # Consistent y-axis range
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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=500
)
# Display the interactive plot in Streamlit
st.plotly_chart(fig, use_container_width=True)
# Tab for real parts
with prev_real_tab:
# Find the min and max for symmetric y-axis range
real_min = min(np.min(real_values1), np.min(real_values2), np.min(real_values3))
real_max = max(np.max(real_values1), np.max(real_values2), np.max(real_values3))
y_range = max(abs(real_min), abs(real_max))
# Create an interactive plot for real parts
real_fig = go.Figure()
# Add traces for each root's real part
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values1,
mode='lines',
name='Re(sβ)',
line=dict(color=color_max, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 1'
))
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values2,
mode='lines',
name='Re(sβ)',
line=dict(color=color_min, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 2'
))
real_fig.add_trace(go.Scatter(
x=z_values,
y=real_values3,
mode='lines',
name='Re(sβ)',
line=dict(color=color_theory_max, width=3),
hovertemplate='z: %{x:.3f}
Re(sβ): %{y:.6f}
Root 3'
))
# Add zero line for reference
real_fig.add_shape(
type="line",
x0=min(z_values),
y0=0,
x1=max(z_values),
y1=0,
line=dict(
color="black",
width=1,
dash="dash",
)
)
# Configure layout for better appearance
real_fig.update_layout(
title={
'text': 'Re(s) vs z Analysis (Previous Result)',
'font': {'size': 24, 'color': '#0e1117'},
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top'
},
xaxis={
'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'type': 'log'
},
yaxis={
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
'tickfont': {'size': 14},
'gridcolor': 'rgba(220, 220, 220, 0.5)',
'showgrid': True,
'range': [-y_range * 1.1, y_range * 1.1] # Symmetric range with padding
},
plot_bgcolor='rgba(250, 250, 250, 0.8)',
paper_bgcolor='rgba(255, 255, 255, 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=500
)
# Display the interactive plot in Streamlit
st.plotly_chart(real_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 Im(s) vs z Analysis' to create a visualization.")
else:
# Show placeholder
st.info("π Set parameters and click 'Generate Im(s) vs z Analysis' to create a visualization.")
st.markdown('
', unsafe_allow_html=True)
# Add footer with instructions
st.markdown("""
""", unsafe_allow_html=True)