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)
|