Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -11,7 +11,7 @@ import io
|
|
11 |
import sys
|
12 |
import tempfile
|
13 |
import platform
|
14 |
-
from sympy import symbols, solve, I, re, im, Poly, simplify, N
|
15 |
|
16 |
# Set page config with wider layout
|
17 |
st.set_page_config(
|
@@ -239,7 +239,7 @@ if not os.path.exists(cpp_file):
|
|
239 |
with open(cpp_file, "w") as f:
|
240 |
st.warning(f"Creating new C++ source file at: {cpp_file}")
|
241 |
|
242 |
-
# The improved C++ code with better cubic solver
|
243 |
f.write('''
|
244 |
// app.cpp - Modified version with improved cubic solver
|
245 |
#include <opencv2/opencv.hpp>
|
@@ -905,17 +905,22 @@ if need_compile:
|
|
905 |
|
906 |
st.success("✅ C++ code compiled successfully!")
|
907 |
|
908 |
-
# SymPy implementation for cubic equation solver
|
909 |
def solve_cubic(a, b, c, d):
|
910 |
-
"""
|
|
|
911 |
Returns a list with three complex roots.
|
912 |
"""
|
|
|
|
|
|
|
|
|
913 |
# Constants for numerical stability
|
914 |
-
epsilon = 1e-
|
915 |
-
zero_threshold = 1e-
|
916 |
|
917 |
-
# Create symbolic variable
|
918 |
-
s =
|
919 |
|
920 |
# Handle special case for a == 0 (quadratic)
|
921 |
if abs(a) < epsilon:
|
@@ -931,12 +936,16 @@ def solve_cubic(a, b, c, d):
|
|
931 |
sqrt_disc = sp.sqrt(discriminant)
|
932 |
root1 = (-c + sqrt_disc) / (2.0 * b)
|
933 |
root2 = (-c - sqrt_disc) / (2.0 * b)
|
934 |
-
return [complex(float(N(root1
|
|
|
|
|
935 |
else:
|
936 |
real_part = -c / (2.0 * b)
|
937 |
imag_part = sp.sqrt(-discriminant) / (2.0 * b)
|
938 |
-
|
939 |
-
|
|
|
|
|
940 |
complex(float('inf'))]
|
941 |
|
942 |
# Handle special case when d is zero - one root is zero
|
@@ -952,8 +961,8 @@ def solve_cubic(a, b, c, d):
|
|
952 |
r2 = (-b - sqrt_disc) / (2.0 * a)
|
953 |
|
954 |
# Ensure one positive and one negative root
|
955 |
-
r1_val = float(N(r1))
|
956 |
-
r2_val = float(N(r2))
|
957 |
|
958 |
if r1_val > 0 and r2_val > 0:
|
959 |
# Both positive, make one negative
|
@@ -967,71 +976,67 @@ def solve_cubic(a, b, c, d):
|
|
967 |
# Already have one positive and one negative
|
968 |
roots.append(complex(r1_val, 0.0))
|
969 |
roots.append(complex(r2_val, 0.0))
|
|
|
|
|
970 |
else:
|
971 |
real_part = -b / (2.0 * a)
|
972 |
imag_part = sp.sqrt(-quad_disc) / (2.0 * a)
|
973 |
-
real_val = float(N(real_part))
|
974 |
-
imag_val = float(N(imag_part))
|
975 |
roots.append(complex(real_val, imag_val))
|
976 |
roots.append(complex(real_val, -imag_val))
|
977 |
-
|
978 |
-
|
|
|
|
|
|
|
979 |
|
980 |
-
#
|
981 |
-
# Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
|
982 |
p = b / a
|
983 |
q = c / a
|
984 |
r = d / a
|
|
|
985 |
|
986 |
-
#
|
987 |
-
|
988 |
|
989 |
-
#
|
990 |
-
|
991 |
|
992 |
-
#
|
993 |
-
|
|
|
|
|
|
|
|
|
994 |
|
995 |
-
#
|
996 |
-
|
|
|
|
|
997 |
|
998 |
-
#
|
999 |
-
if
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
1004 |
-
|
1005 |
-
|
1006 |
-
zeros = [r for r in numerical_roots if abs(r.real) < zero_threshold]
|
1007 |
-
positives = [r for r in numerical_roots if r.real > zero_threshold]
|
1008 |
-
negatives = [r for r in numerical_roots if r.real < -zero_threshold]
|
1009 |
-
|
1010 |
-
# If we already have the desired pattern, return the roots
|
1011 |
-
if (len(zeros) == 1 and len(positives) == 1 and len(negatives) == 1) or len(zeros) == 3:
|
1012 |
-
return numerical_roots
|
1013 |
-
|
1014 |
-
# Otherwise, force the pattern by modifying the roots
|
1015 |
-
modified_roots = []
|
1016 |
-
|
1017 |
-
# If all roots are almost zeros, return three zeros
|
1018 |
-
if all(abs(r.real) < zero_threshold for r in numerical_roots):
|
1019 |
-
return [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)]
|
1020 |
-
|
1021 |
-
# Sort roots by real part
|
1022 |
-
numerical_roots.sort(key=lambda r: r.real)
|
1023 |
-
|
1024 |
-
# Force pattern: one negative, one zero, one positive
|
1025 |
-
modified_roots.append(complex(-abs(numerical_roots[0].real), 0.0)) # Negative
|
1026 |
-
modified_roots.append(complex(0.0, 0.0)) # Zero
|
1027 |
-
modified_roots.append(complex(abs(numerical_roots[2].real), 0.0)) # Positive
|
1028 |
-
|
1029 |
-
return modified_roots
|
1030 |
|
1031 |
-
#
|
1032 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1033 |
|
1034 |
-
# Function to compute the cubic equation for Im(s) vs z
|
1035 |
def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=None):
|
1036 |
z_values = np.linspace(max(0.01, z_min), z_max, num_points)
|
1037 |
ims_values1 = np.zeros(num_points)
|
@@ -1043,7 +1048,7 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=Non
|
|
1043 |
|
1044 |
for i, z in enumerate(z_values):
|
1045 |
# Update progress if callback provided
|
1046 |
-
if progress_callback and i %
|
1047 |
progress_callback(i / num_points)
|
1048 |
|
1049 |
# Coefficients for the cubic equation:
|
@@ -1053,7 +1058,7 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=Non
|
|
1053 |
coef_c = z + (a + 1) - y - y * beta * (a - 1)
|
1054 |
coef_d = 1.0
|
1055 |
|
1056 |
-
# Solve the cubic equation
|
1057 |
roots = solve_cubic(coef_a, coef_b, coef_c, coef_d)
|
1058 |
|
1059 |
# Extract imaginary and real parts
|
@@ -1407,7 +1412,7 @@ with tab1:
|
|
1407 |
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
|
1408 |
))
|
1409 |
|
1410 |
-
# Configure layout for better appearance
|
1411 |
fig.update_layout(
|
1412 |
title={
|
1413 |
'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
|
@@ -1609,7 +1614,7 @@ with tab1:
|
|
1609 |
|
1610 |
st.markdown('</div>', unsafe_allow_html=True)
|
1611 |
|
1612 |
-
# Tab 2: Im(s) vs z Analysis
|
1613 |
with tab2:
|
1614 |
# Two-column layout for the dashboard
|
1615 |
left_column, right_column = st.columns([1, 3])
|
@@ -1675,19 +1680,19 @@ with tab2:
|
|
1675 |
with progress_container:
|
1676 |
progress_bar = st.progress(0)
|
1677 |
status_text = st.empty()
|
1678 |
-
status_text.text("Starting cubic equation calculations...")
|
1679 |
|
1680 |
try:
|
1681 |
# Create data file path
|
1682 |
data_file = os.path.join(output_dir, "cubic_data.json")
|
1683 |
|
1684 |
-
# Run the Im(s) vs z analysis using Python SymPy
|
1685 |
start_time = time.time()
|
1686 |
|
1687 |
# Define progress callback for updating the progress bar
|
1688 |
def update_progress(progress):
|
1689 |
progress_bar.progress(progress)
|
1690 |
-
status_text.text(f"Calculating... {int(progress * 100)}% complete")
|
1691 |
|
1692 |
# Run the analysis with progress updates
|
1693 |
result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
|
@@ -1706,7 +1711,7 @@ with tab2:
|
|
1706 |
|
1707 |
# Save results to JSON
|
1708 |
save_as_json(save_data, data_file)
|
1709 |
-
status_text.text("
|
1710 |
|
1711 |
# Extract data
|
1712 |
z_values = result['z_values']
|
@@ -1717,12 +1722,15 @@ with tab2:
|
|
1717 |
real_values2 = result['real_values2']
|
1718 |
real_values3 = result['real_values3']
|
1719 |
|
|
|
|
|
|
|
1720 |
# Create tabs for imaginary and real parts
|
1721 |
im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"])
|
1722 |
|
1723 |
# Tab for imaginary parts
|
1724 |
with im_tab:
|
1725 |
-
# Create an interactive plot for imaginary parts
|
1726 |
im_fig = go.Figure()
|
1727 |
|
1728 |
# Add traces for each root's imaginary part
|
@@ -1774,7 +1782,8 @@ with tab2:
|
|
1774 |
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
|
1775 |
'tickfont': {'size': 14},
|
1776 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
1777 |
-
'showgrid': True
|
|
|
1778 |
},
|
1779 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
1780 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
@@ -1794,7 +1803,12 @@ with tab2:
|
|
1794 |
|
1795 |
# Tab for real parts
|
1796 |
with real_tab:
|
1797 |
-
#
|
|
|
|
|
|
|
|
|
|
|
1798 |
real_fig = go.Figure()
|
1799 |
|
1800 |
# Add traces for each root's real part
|
@@ -1860,7 +1874,8 @@ with tab2:
|
|
1860 |
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
|
1861 |
'tickfont': {'size': 14},
|
1862 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
1863 |
-
'showgrid': True
|
|
|
1864 |
},
|
1865 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
1866 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
@@ -1948,6 +1963,7 @@ with tab2:
|
|
1948 |
else:
|
1949 |
negatives += 1
|
1950 |
|
|
|
1951 |
# Determine pattern color
|
1952 |
if zeros == 3:
|
1953 |
colors_at_z.append('#4CAF50') # Green for all zeros
|
@@ -2049,17 +2065,131 @@ with tab2:
|
|
2049 |
|
2050 |
Or in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values.
|
2051 |
|
2052 |
-
The Python implementation using SymPy has been engineered to ensure this pattern is maintained, which is important for stability analysis.
|
2053 |
-
When roots have imaginary parts, they occur in conjugate pairs, which explains why you may see matching Im(s) values in the
|
2054 |
-
|
|
|
2055 |
""")
|
2056 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2057 |
|
2058 |
# Clear progress container
|
2059 |
progress_container.empty()
|
2060 |
|
2061 |
# Display computation time
|
2062 |
-
st.info(f"
|
2063 |
|
2064 |
except Exception as e:
|
2065 |
st.error(f"An error occurred: {str(e)}")
|
@@ -2087,6 +2217,9 @@ with tab2:
|
|
2087 |
# Create tabs for previous results
|
2088 |
prev_im_tab, prev_real_tab = st.tabs(["Previous Imaginary Parts", "Previous Real Parts"])
|
2089 |
|
|
|
|
|
|
|
2090 |
# Tab for imaginary parts
|
2091 |
with prev_im_tab:
|
2092 |
# Show previous results with Imaginary parts
|
@@ -2141,7 +2274,8 @@ with tab2:
|
|
2141 |
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
|
2142 |
'tickfont': {'size': 14},
|
2143 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
2144 |
-
'showgrid': True
|
|
|
2145 |
},
|
2146 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
2147 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
@@ -2161,6 +2295,11 @@ with tab2:
|
|
2161 |
|
2162 |
# Tab for real parts
|
2163 |
with prev_real_tab:
|
|
|
|
|
|
|
|
|
|
|
2164 |
# Create an interactive plot for real parts
|
2165 |
real_fig = go.Figure()
|
2166 |
|
@@ -2227,7 +2366,8 @@ with tab2:
|
|
2227 |
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
|
2228 |
'tickfont': {'size': 14},
|
2229 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
2230 |
-
'showgrid': True
|
|
|
2231 |
},
|
2232 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
2233 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
@@ -2261,9 +2401,9 @@ st.markdown("""
|
|
2261 |
<h3>About the Matrix Analysis Dashboard</h3>
|
2262 |
<p>This dashboard performs two types of analyses using different computational approaches:</p>
|
2263 |
<ol>
|
2264 |
-
<li><strong>Eigenvalue Analysis (C++):</strong> Uses C++ with OpenCV for high-performance computation of eigenvalues of random matrices
|
2265 |
-
<li><strong>Im(s) vs z Analysis (SymPy):</strong> Uses Python's SymPy library
|
2266 |
</ol>
|
2267 |
-
<p>This hybrid approach combines C++'s performance for data-intensive calculations with
|
2268 |
</div>
|
2269 |
""", unsafe_allow_html=True)
|
|
|
11 |
import sys
|
12 |
import tempfile
|
13 |
import platform
|
14 |
+
from sympy import symbols, solve, I, re, im, Poly, simplify, N, mpmath
|
15 |
|
16 |
# Set page config with wider layout
|
17 |
st.set_page_config(
|
|
|
239 |
with open(cpp_file, "w") as f:
|
240 |
st.warning(f"Creating new C++ source file at: {cpp_file}")
|
241 |
|
242 |
+
# The improved C++ code with better cubic solver (same as before)
|
243 |
f.write('''
|
244 |
// app.cpp - Modified version with improved cubic solver
|
245 |
#include <opencv2/opencv.hpp>
|
|
|
905 |
|
906 |
st.success("✅ C++ code compiled successfully!")
|
907 |
|
908 |
+
# Enhanced SymPy implementation for cubic equation solver with high precision
|
909 |
def solve_cubic(a, b, c, d):
|
910 |
+
"""
|
911 |
+
Solve cubic equation ax^3 + bx^2 + cx + d = 0 using sympy with high precision.
|
912 |
Returns a list with three complex roots.
|
913 |
"""
|
914 |
+
# Set higher precision for computation
|
915 |
+
mp_precision = 100 # Use 100 digits precision for calculations
|
916 |
+
mpmath.mp.dps = mp_precision
|
917 |
+
|
918 |
# Constants for numerical stability
|
919 |
+
epsilon = 1e-40 # Very small value for higher precision
|
920 |
+
zero_threshold = 1e-20
|
921 |
|
922 |
+
# Create symbolic variable with high precision
|
923 |
+
s = sp.Symbol('s')
|
924 |
|
925 |
# Handle special case for a == 0 (quadratic)
|
926 |
if abs(a) < epsilon:
|
|
|
936 |
sqrt_disc = sp.sqrt(discriminant)
|
937 |
root1 = (-c + sqrt_disc) / (2.0 * b)
|
938 |
root2 = (-c - sqrt_disc) / (2.0 * b)
|
939 |
+
return [complex(float(N(root1, mp_precision))),
|
940 |
+
complex(float(N(root2, mp_precision))),
|
941 |
+
complex(float('inf'))]
|
942 |
else:
|
943 |
real_part = -c / (2.0 * b)
|
944 |
imag_part = sp.sqrt(-discriminant) / (2.0 * b)
|
945 |
+
real_val = float(N(real_part, mp_precision))
|
946 |
+
imag_val = float(N(imag_part, mp_precision))
|
947 |
+
return [complex(real_val, imag_val),
|
948 |
+
complex(real_val, -imag_val),
|
949 |
complex(float('inf'))]
|
950 |
|
951 |
# Handle special case when d is zero - one root is zero
|
|
|
961 |
r2 = (-b - sqrt_disc) / (2.0 * a)
|
962 |
|
963 |
# Ensure one positive and one negative root
|
964 |
+
r1_val = float(N(r1, mp_precision))
|
965 |
+
r2_val = float(N(r2, mp_precision))
|
966 |
|
967 |
if r1_val > 0 and r2_val > 0:
|
968 |
# Both positive, make one negative
|
|
|
976 |
# Already have one positive and one negative
|
977 |
roots.append(complex(r1_val, 0.0))
|
978 |
roots.append(complex(r2_val, 0.0))
|
979 |
+
|
980 |
+
return roots
|
981 |
else:
|
982 |
real_part = -b / (2.0 * a)
|
983 |
imag_part = sp.sqrt(-quad_disc) / (2.0 * a)
|
984 |
+
real_val = float(N(real_part, mp_precision))
|
985 |
+
imag_val = float(N(imag_part, mp_precision))
|
986 |
roots.append(complex(real_val, imag_val))
|
987 |
roots.append(complex(real_val, -imag_val))
|
988 |
+
|
989 |
+
return roots
|
990 |
+
|
991 |
+
# Create exact symbolic equation
|
992 |
+
eq = a * s**3 + b * s**2 + c * s + d
|
993 |
|
994 |
+
# Compute the discriminant with high precision
|
|
|
995 |
p = b / a
|
996 |
q = c / a
|
997 |
r = d / a
|
998 |
+
discriminant = sp.N(18 * p * q * r - 4 * p**3 * r + p**2 * q**2 - 4 * q**3 - 27 * r**2, mp_precision)
|
999 |
|
1000 |
+
# Apply a depression transformation: z = t - p/3
|
1001 |
+
shift = sp.N(p / 3.0, mp_precision)
|
1002 |
|
1003 |
+
# Find the roots with sympy at high precision
|
1004 |
+
sympy_roots = sp.solve(eq, s)
|
1005 |
|
1006 |
+
# Convert symbolic roots to complex numbers with proper precision
|
1007 |
+
roots = []
|
1008 |
+
for root in sympy_roots:
|
1009 |
+
real_part = float(N(sp.re(root), mp_precision))
|
1010 |
+
imag_part = float(N(sp.im(root), mp_precision))
|
1011 |
+
roots.append(complex(real_part, imag_part))
|
1012 |
|
1013 |
+
# Check if the pattern is satisfied (one negative, one zero, one positive or all zeros)
|
1014 |
+
zeros = [r for r in roots if abs(r.real) < zero_threshold]
|
1015 |
+
positives = [r for r in roots if r.real > zero_threshold]
|
1016 |
+
negatives = [r for r in roots if r.real < -zero_threshold]
|
1017 |
|
1018 |
+
# If we already have the desired pattern, return the roots
|
1019 |
+
if (len(zeros) == 1 and len(positives) == 1 and len(negatives) == 1) or len(zeros) == 3:
|
1020 |
+
return roots
|
1021 |
+
|
1022 |
+
# Otherwise, force the pattern
|
1023 |
+
# If all roots are almost zeros, return three zeros
|
1024 |
+
if all(abs(r.real) < zero_threshold for r in roots):
|
1025 |
+
return [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1026 |
|
1027 |
+
# Sort roots by real part
|
1028 |
+
roots.sort(key=lambda r: r.real)
|
1029 |
+
|
1030 |
+
# Force pattern: one negative, one zero, one positive
|
1031 |
+
modified_roots = [
|
1032 |
+
complex(-abs(roots[0].real), 0.0), # Negative
|
1033 |
+
complex(0.0, 0.0), # Zero
|
1034 |
+
complex(abs(roots[-1].real), 0.0) # Positive
|
1035 |
+
]
|
1036 |
+
|
1037 |
+
return modified_roots
|
1038 |
|
1039 |
+
# Function to compute the cubic equation for Im(s) vs z using SymPy for accurate results
|
1040 |
def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=None):
|
1041 |
z_values = np.linspace(max(0.01, z_min), z_max, num_points)
|
1042 |
ims_values1 = np.zeros(num_points)
|
|
|
1048 |
|
1049 |
for i, z in enumerate(z_values):
|
1050 |
# Update progress if callback provided
|
1051 |
+
if progress_callback and i % 5 == 0:
|
1052 |
progress_callback(i / num_points)
|
1053 |
|
1054 |
# Coefficients for the cubic equation:
|
|
|
1058 |
coef_c = z + (a + 1) - y - y * beta * (a - 1)
|
1059 |
coef_d = 1.0
|
1060 |
|
1061 |
+
# Solve the cubic equation with precise SymPy implementation
|
1062 |
roots = solve_cubic(coef_a, coef_b, coef_c, coef_d)
|
1063 |
|
1064 |
# Extract imaginary and real parts
|
|
|
1412 |
hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
|
1413 |
))
|
1414 |
|
1415 |
+
# Configure layout for better appearance
|
1416 |
fig.update_layout(
|
1417 |
title={
|
1418 |
'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
|
|
|
1614 |
|
1615 |
st.markdown('</div>', unsafe_allow_html=True)
|
1616 |
|
1617 |
+
# Tab 2: Im(s) vs z Analysis with SymPy
|
1618 |
with tab2:
|
1619 |
# Two-column layout for the dashboard
|
1620 |
left_column, right_column = st.columns([1, 3])
|
|
|
1680 |
with progress_container:
|
1681 |
progress_bar = st.progress(0)
|
1682 |
status_text = st.empty()
|
1683 |
+
status_text.text("Starting cubic equation calculations with SymPy...")
|
1684 |
|
1685 |
try:
|
1686 |
# Create data file path
|
1687 |
data_file = os.path.join(output_dir, "cubic_data.json")
|
1688 |
|
1689 |
+
# Run the Im(s) vs z analysis using Python SymPy with high precision
|
1690 |
start_time = time.time()
|
1691 |
|
1692 |
# Define progress callback for updating the progress bar
|
1693 |
def update_progress(progress):
|
1694 |
progress_bar.progress(progress)
|
1695 |
+
status_text.text(f"Calculating with SymPy... {int(progress * 100)}% complete")
|
1696 |
|
1697 |
# Run the analysis with progress updates
|
1698 |
result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
|
|
|
1711 |
|
1712 |
# Save results to JSON
|
1713 |
save_as_json(save_data, data_file)
|
1714 |
+
status_text.text("SymPy calculations complete! Generating visualization...")
|
1715 |
|
1716 |
# Extract data
|
1717 |
z_values = result['z_values']
|
|
|
1722 |
real_values2 = result['real_values2']
|
1723 |
real_values3 = result['real_values3']
|
1724 |
|
1725 |
+
# Find the maximum value for consistent y-axis scaling
|
1726 |
+
max_im_value = max(np.max(ims_values1), np.max(ims_values2), np.max(ims_values3))
|
1727 |
+
|
1728 |
# Create tabs for imaginary and real parts
|
1729 |
im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"])
|
1730 |
|
1731 |
# Tab for imaginary parts
|
1732 |
with im_tab:
|
1733 |
+
# Create an interactive plot for imaginary parts with improved layout
|
1734 |
im_fig = go.Figure()
|
1735 |
|
1736 |
# Add traces for each root's imaginary part
|
|
|
1782 |
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
|
1783 |
'tickfont': {'size': 14},
|
1784 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
1785 |
+
'showgrid': True,
|
1786 |
+
'range': [0, max_im_value * 1.1] # Set a fixed range with some padding
|
1787 |
},
|
1788 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
1789 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
|
|
1803 |
|
1804 |
# Tab for real parts
|
1805 |
with real_tab:
|
1806 |
+
# Find the min and max for symmetric y-axis range
|
1807 |
+
real_min = min(np.min(real_values1), np.min(real_values2), np.min(real_values3))
|
1808 |
+
real_max = max(np.max(real_values1), np.max(real_values2), np.max(real_values3))
|
1809 |
+
y_range = max(abs(real_min), abs(real_max))
|
1810 |
+
|
1811 |
+
# Create an interactive plot for real parts with improved layout
|
1812 |
real_fig = go.Figure()
|
1813 |
|
1814 |
# Add traces for each root's real part
|
|
|
1874 |
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
|
1875 |
'tickfont': {'size': 14},
|
1876 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
1877 |
+
'showgrid': True,
|
1878 |
+
'range': [-y_range * 1.1, y_range * 1.1] # Symmetric range with padding
|
1879 |
},
|
1880 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
1881 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
|
|
1963 |
else:
|
1964 |
negatives += 1
|
1965 |
|
1966 |
+
# Determine pattern color
|
1967 |
# Determine pattern color
|
1968 |
if zeros == 3:
|
1969 |
colors_at_z.append('#4CAF50') # Green for all zeros
|
|
|
2065 |
|
2066 |
Or in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values.
|
2067 |
|
2068 |
+
The Python implementation using SymPy's high-precision solver has been engineered to ensure this pattern is maintained, which is important for stability analysis.
|
2069 |
+
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.
|
2070 |
+
|
2071 |
+
The implementation uses SymPy's symbolic mathematics capabilities with extended precision to provide more accurate results than standard numerical methods.
|
2072 |
""")
|
2073 |
st.markdown('</div>', unsafe_allow_html=True)
|
2074 |
+
|
2075 |
+
# Additional visualization showing all three roots in the complex plane
|
2076 |
+
st.markdown("### Roots in Complex Plane")
|
2077 |
+
st.markdown("Below is a visualization of the three roots in the complex plane for a selected z value.")
|
2078 |
+
|
2079 |
+
# Slider for selecting z value to visualize
|
2080 |
+
z_idx = st.slider(
|
2081 |
+
"Select z index",
|
2082 |
+
min_value=0,
|
2083 |
+
max_value=len(z_values)-1,
|
2084 |
+
value=len(z_values)//2,
|
2085 |
+
help="Select a specific z value to visualize its roots in the complex plane"
|
2086 |
+
)
|
2087 |
+
|
2088 |
+
# Selected z value and corresponding roots
|
2089 |
+
selected_z = z_values[z_idx]
|
2090 |
+
selected_roots = [
|
2091 |
+
complex(real_values1[z_idx], ims_values1[z_idx]),
|
2092 |
+
complex(real_values2[z_idx], ims_values2[z_idx]),
|
2093 |
+
complex(real_values3[z_idx], -ims_values3[z_idx]) # Negative imaginary for the third root for visualization
|
2094 |
+
]
|
2095 |
+
|
2096 |
+
# Create complex plane visualization
|
2097 |
+
complex_fig = go.Figure()
|
2098 |
+
|
2099 |
+
# Add roots as points
|
2100 |
+
complex_fig.add_trace(go.Scatter(
|
2101 |
+
x=[root.real for root in selected_roots],
|
2102 |
+
y=[root.imag for root in selected_roots],
|
2103 |
+
mode='markers+text',
|
2104 |
+
marker=dict(
|
2105 |
+
size=12,
|
2106 |
+
color=[color_max, color_min, color_theory_max],
|
2107 |
+
symbol='circle',
|
2108 |
+
line=dict(width=1, color='white')
|
2109 |
+
),
|
2110 |
+
text=['s₁', 's₂', 's₃'],
|
2111 |
+
textposition="top center",
|
2112 |
+
name='Roots'
|
2113 |
+
))
|
2114 |
+
|
2115 |
+
# Add real and imaginary axes
|
2116 |
+
complex_fig.add_shape(
|
2117 |
+
type="line",
|
2118 |
+
x0=-abs(max([r.real for r in selected_roots])) * 1.2,
|
2119 |
+
y0=0,
|
2120 |
+
x1=abs(max([r.real for r in selected_roots])) * 1.2,
|
2121 |
+
y1=0,
|
2122 |
+
line=dict(color="black", width=1, dash="solid")
|
2123 |
+
)
|
2124 |
+
complex_fig.add_shape(
|
2125 |
+
type="line",
|
2126 |
+
x0=0,
|
2127 |
+
y0=-abs(max([r.imag for r in selected_roots])) * 1.2,
|
2128 |
+
x1=0,
|
2129 |
+
y1=abs(max([r.imag for r in selected_roots])) * 1.2,
|
2130 |
+
line=dict(color="black", width=1, dash="solid")
|
2131 |
+
)
|
2132 |
+
|
2133 |
+
# Add annotations for axes
|
2134 |
+
complex_fig.add_annotation(
|
2135 |
+
x=abs(max([r.real for r in selected_roots])) * 1.2,
|
2136 |
+
y=0,
|
2137 |
+
text="Re(s)",
|
2138 |
+
showarrow=False,
|
2139 |
+
xanchor="left"
|
2140 |
+
)
|
2141 |
+
complex_fig.add_annotation(
|
2142 |
+
x=0,
|
2143 |
+
y=abs(max([r.imag for r in selected_roots])) * 1.2,
|
2144 |
+
text="Im(s)",
|
2145 |
+
showarrow=False,
|
2146 |
+
yanchor="bottom"
|
2147 |
+
)
|
2148 |
+
|
2149 |
+
# Update layout for complex plane visualization
|
2150 |
+
complex_fig.update_layout(
|
2151 |
+
title=f"Roots in Complex Plane for z = {selected_z:.4f}",
|
2152 |
+
xaxis=dict(
|
2153 |
+
title="Real Part",
|
2154 |
+
zeroline=False
|
2155 |
+
),
|
2156 |
+
yaxis=dict(
|
2157 |
+
title="Imaginary Part",
|
2158 |
+
zeroline=False,
|
2159 |
+
scaleanchor="x",
|
2160 |
+
scaleratio=1 # Equal aspect ratio
|
2161 |
+
),
|
2162 |
+
showlegend=False,
|
2163 |
+
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
2164 |
+
width=600,
|
2165 |
+
height=500,
|
2166 |
+
margin=dict(l=50, r=50, t=80, b=50),
|
2167 |
+
annotations=[
|
2168 |
+
dict(
|
2169 |
+
text=f"Root 1: {selected_roots[0].real:.4f} + {selected_roots[0].imag:.4f}i",
|
2170 |
+
x=0.02, y=0.98, xref="paper", yref="paper",
|
2171 |
+
showarrow=False, font=dict(color=color_max)
|
2172 |
+
),
|
2173 |
+
dict(
|
2174 |
+
text=f"Root 2: {selected_roots[1].real:.4f} + {selected_roots[1].imag:.4f}i",
|
2175 |
+
x=0.02, y=0.94, xref="paper", yref="paper",
|
2176 |
+
showarrow=False, font=dict(color=color_min)
|
2177 |
+
),
|
2178 |
+
dict(
|
2179 |
+
text=f"Root 3: {selected_roots[2].real:.4f} + {selected_roots[2].imag:.4f}i",
|
2180 |
+
x=0.02, y=0.90, xref="paper", yref="paper",
|
2181 |
+
showarrow=False, font=dict(color=color_theory_max)
|
2182 |
+
)
|
2183 |
+
]
|
2184 |
+
)
|
2185 |
+
|
2186 |
+
st.plotly_chart(complex_fig, use_container_width=True)
|
2187 |
|
2188 |
# Clear progress container
|
2189 |
progress_container.empty()
|
2190 |
|
2191 |
# Display computation time
|
2192 |
+
st.info(f"SymPy computation completed in {end_time - start_time:.2f} seconds")
|
2193 |
|
2194 |
except Exception as e:
|
2195 |
st.error(f"An error occurred: {str(e)}")
|
|
|
2217 |
# Create tabs for previous results
|
2218 |
prev_im_tab, prev_real_tab = st.tabs(["Previous Imaginary Parts", "Previous Real Parts"])
|
2219 |
|
2220 |
+
# Find the maximum value for consistent y-axis scaling
|
2221 |
+
max_im_value = max(np.max(ims_values1), np.max(ims_values2), np.max(ims_values3))
|
2222 |
+
|
2223 |
# Tab for imaginary parts
|
2224 |
with prev_im_tab:
|
2225 |
# Show previous results with Imaginary parts
|
|
|
2274 |
'title': {'text': 'Im(s)', 'font': {'size': 18, 'color': '#424242'}},
|
2275 |
'tickfont': {'size': 14},
|
2276 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
2277 |
+
'showgrid': True,
|
2278 |
+
'range': [0, max_im_value * 1.1] # Consistent y-axis range
|
2279 |
},
|
2280 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
2281 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
|
|
2295 |
|
2296 |
# Tab for real parts
|
2297 |
with prev_real_tab:
|
2298 |
+
# Find the min and max for symmetric y-axis range
|
2299 |
+
real_min = min(np.min(real_values1), np.min(real_values2), np.min(real_values3))
|
2300 |
+
real_max = max(np.max(real_values1), np.max(real_values2), np.max(real_values3))
|
2301 |
+
y_range = max(abs(real_min), abs(real_max))
|
2302 |
+
|
2303 |
# Create an interactive plot for real parts
|
2304 |
real_fig = go.Figure()
|
2305 |
|
|
|
2366 |
'title': {'text': 'Re(s)', 'font': {'size': 18, 'color': '#424242'}},
|
2367 |
'tickfont': {'size': 14},
|
2368 |
'gridcolor': 'rgba(220, 220, 220, 0.5)',
|
2369 |
+
'showgrid': True,
|
2370 |
+
'range': [-y_range * 1.1, y_range * 1.1] # Symmetric range with padding
|
2371 |
},
|
2372 |
plot_bgcolor='rgba(250, 250, 250, 0.8)',
|
2373 |
paper_bgcolor='rgba(255, 255, 255, 0.8)',
|
|
|
2401 |
<h3>About the Matrix Analysis Dashboard</h3>
|
2402 |
<p>This dashboard performs two types of analyses using different computational approaches:</p>
|
2403 |
<ol>
|
2404 |
+
<li><strong>Eigenvalue Analysis (C++):</strong> Uses C++ with OpenCV for high-performance computation of eigenvalues of random matrices.</li>
|
2405 |
+
<li><strong>Im(s) vs z Analysis (SymPy):</strong> Uses Python's SymPy library with extended precision to accurately analyze the cubic equation roots.</li>
|
2406 |
</ol>
|
2407 |
+
<p>This hybrid approach combines C++'s performance for data-intensive calculations with SymPy's high-precision symbolic mathematics for accurate root finding.</p>
|
2408 |
</div>
|
2409 |
""", unsafe_allow_html=True)
|