euler314 commited on
Commit
88f0d0e
·
verified ·
1 Parent(s): ce9d199

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +438 -275
app.py CHANGED
@@ -12,12 +12,14 @@ import io
12
  import sys
13
  import tempfile
14
  import platform
15
- from sympy import symbols, solve, I, re, im, Poly, simplify, N, mpmath
 
 
16
 
17
  # Set page config with wider layout
18
  st.set_page_config(
19
  page_title="Matrix Analysis Dashboard",
20
- page_icon="📊",
21
  layout="wide",
22
  initial_sidebar_state="expanded"
23
  )
@@ -388,7 +390,7 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
388
  }
389
 
390
  if (std::abs(delta0) < zero_threshold) {
391
- // Delta0 ≈ 0: One double root and one simple root
392
  double simple = std::cbrt(-delta1);
393
  double doubleRoot = -simple/2 - shift;
394
  double simpleRoot = simple - shift;
@@ -716,21 +718,21 @@ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
716
  << ", theory_tolerance = " << theory_tolerance << std::endl;
717
  std::cout << "Output will be saved to: " << output_file << std::endl;
718
 
719
- // ─── Beta range parameters ────────────────────────────────────────
720
  const int num_beta_points = fineness; // Controlled by fineness parameter
721
  std::vector<double> beta_values(num_beta_points);
722
  for (int i = 0; i < num_beta_points; ++i) {
723
  beta_values[i] = static_cast<double>(i) / (num_beta_points - 1);
724
  }
725
 
726
- // ─── Storage for results ────────────────────────────────────────
727
  std::vector<double> max_eigenvalues(num_beta_points);
728
  std::vector<double> min_eigenvalues(num_beta_points);
729
  std::vector<double> theoretical_max_values(num_beta_points);
730
  std::vector<double> theoretical_min_values(num_beta_points);
731
 
732
  try {
733
- // ─── Random‐Gaussian X and S_n ────────────────────────────────
734
  std::random_device rd;
735
  std::mt19937_64 rng{rd()};
736
  std::normal_distribution<double> norm(0.0, 1.0);
@@ -740,7 +742,7 @@ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
740
  for(int j = 0; j < n; ++j)
741
  X.at<double>(i,j) = norm(rng);
742
 
743
- // ─── Process each beta value ─────────────────────────────────
744
  for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) {
745
  double beta = beta_values[beta_idx];
746
 
@@ -748,7 +750,7 @@ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
748
  theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
749
  theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
750
 
751
- // ─── Build T_n matrix ──────────────────────────────────
752
  int k = static_cast<int>(std::floor(beta * p));
753
  std::vector<double> diags(p, 1.0);
754
  std::fill_n(diags.begin(), k, a);
@@ -759,10 +761,10 @@ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
759
  T_n.at<double>(i,i) = diags[i];
760
  }
761
 
762
- // ─── Form B_n = (1/n) * X * T_n * X^T ────────────
763
  cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
764
 
765
- // ─── Compute eigenvalues of B ────────────────────────────
766
  cv::Mat eigVals;
767
  cv::eigen(B, eigVals);
768
  std::vector<double> eigs(n);
@@ -820,7 +822,7 @@ int main(int argc, char* argv[]) {
820
 
821
  try {
822
  if (mode == "eigenvalues") {
823
- // ─── Eigenvalue analysis mode ───────────────────────────────────────────
824
  if (argc != 10) {
825
  std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl;
826
  std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
@@ -857,14 +859,13 @@ int main(int argc, char* argv[]) {
857
 
858
  # Compile the C++ code with the right OpenCV libraries
859
  st.sidebar.title("Dashboard Settings")
860
- need_compile = not os.path.exists(executable) or st.sidebar.button("🔄 Recompile C++ Code")
861
 
862
  if need_compile:
863
  with st.sidebar:
864
  with st.spinner("Compiling C++ code..."):
865
  # Try to detect the OpenCV installation
866
- opencv_detection_cmd = ["pk
867
- g-config", "--cflags", "--libs", "opencv4"]
868
  opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False)
869
 
870
  compile_commands = []
@@ -892,11 +893,11 @@ g-config", "--cflags", "--libs", "opencv4"]
892
 
893
  if success:
894
  compiled = True
895
- st.success(f"✅ Successfully compiled with: {cmd}")
896
  break
897
 
898
  if not compiled:
899
- st.error("❌ All compilation attempts failed.")
900
  with st.expander("Compilation Details"):
901
  st.code(compile_output)
902
  st.stop()
@@ -905,7 +906,7 @@ g-config", "--cflags", "--libs", "opencv4"]
905
  if platform.system() != "Windows":
906
  os.chmod(executable, 0o755)
907
 
908
- st.success("✅ C++ code compiled successfully!")
909
 
910
  # Set higher precision for mpmath
911
  mpmath.mp.dps = 100 # 100 digits of precision
@@ -1043,7 +1044,7 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=Non
1043
  progress_callback(i / num_points)
1044
 
1045
  # Coefficients for the cubic equation:
1046
- # zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
1047
  coef_a = z * a
1048
  coef_b = z * (a + 1) + a * (1 - y)
1049
  coef_c = z + (a + 1) - y - y * beta * (a - 1)
@@ -1117,8 +1118,8 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1117
  rows=2,
1118
  cols=1,
1119
  subplot_titles=(
1120
- f"Imaginary Parts of Roots: a={cubic_a}, y={cubic_y}, β={cubic_beta}",
1121
- f"Real Parts of Roots: a={cubic_a}, y={cubic_y}, β={cubic_beta}"
1122
  ),
1123
  vertical_spacing=0.15,
1124
  specs=[[{"type": "scatter"}], [{"type": "scatter"}]]
@@ -1130,9 +1131,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1130
  x=z_values,
1131
  y=ims_values1,
1132
  mode='lines',
1133
- name='Im(s₁)',
1134
  line=dict(color='rgb(239, 85, 59)', width=2.5),
1135
- hovertemplate='z: %{x:.4f}<br>Im(s₁): %{y:.6f}<extra>Root 1</extra>'
1136
  ),
1137
  row=1, col=1
1138
  )
@@ -1142,9 +1143,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1142
  x=z_values,
1143
  y=ims_values2,
1144
  mode='lines',
1145
- name='Im(s₂)',
1146
  line=dict(color='rgb(0, 129, 201)', width=2.5),
1147
- hovertemplate='z: %{x:.4f}<br>Im(s₂): %{y:.6f}<extra>Root 2</extra>'
1148
  ),
1149
  row=1, col=1
1150
  )
@@ -1154,9 +1155,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1154
  x=z_values,
1155
  y=ims_values3,
1156
  mode='lines',
1157
- name='Im(s₃)',
1158
  line=dict(color='rgb(0, 176, 80)', width=2.5),
1159
- hovertemplate='z: %{x:.4f}<br>Im(s₃): %{y:.6f}<extra>Root 3</extra>'
1160
  ),
1161
  row=1, col=1
1162
  )
@@ -1167,9 +1168,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1167
  x=z_values,
1168
  y=real_values1,
1169
  mode='lines',
1170
- name='Re(s₁)',
1171
  line=dict(color='rgb(239, 85, 59)', width=2.5),
1172
- hovertemplate='z: %{x:.4f}<br>Re(s₁): %{y:.6f}<extra>Root 1</extra>'
1173
  ),
1174
  row=2, col=1
1175
  )
@@ -1179,9 +1180,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1179
  x=z_values,
1180
  y=real_values2,
1181
  mode='lines',
1182
- name='Re(s₂)',
1183
  line=dict(color='rgb(0, 129, 201)', width=2.5),
1184
- hovertemplate='z: %{x:.4f}<br>Re(s₂): %{y:.6f}<extra>Root 2</extra>'
1185
  ),
1186
  row=2, col=1
1187
  )
@@ -1191,9 +1192,9 @@ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1191
  x=z_values,
1192
  y=real_values3,
1193
  mode='lines',
1194
- name='Re(s₃)',
1195
  line=dict(color='rgb(0, 176, 80)', width=2.5),
1196
- hovertemplate='z: %{x:.4f}<br>Re(s₃): %{y:.6f}<extra>Root 3</extra>'
1197
  ),
1198
  row=2, col=1
1199
  )
@@ -1483,7 +1484,7 @@ def create_complex_plane_visualization(result, z_idx):
1483
  symbol='circle',
1484
  line=dict(width=1, color='black')
1485
  ),
1486
- text=['s₁', 's₂', 's₃'],
1487
  textposition="top center",
1488
  name='Roots'
1489
  ))
@@ -1578,7 +1579,240 @@ def create_complex_plane_visualization(result, z_idx):
1578
 
1579
  return fig
1580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1581
  # Options for theme and appearance
 
 
1582
  with st.sidebar.expander("Theme & Appearance"):
1583
  show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots")
1584
  color_theme = st.selectbox(
@@ -1615,7 +1849,7 @@ with st.sidebar.expander("Theme & Appearance"):
1615
  color_theory_min = 'rgb(180, 30, 180)'
1616
 
1617
  # Create tabs for different analyses
1618
- tab1, tab2 = st.tabs(["📊 Eigenvalue Analysis (C++)", "📈 Im(s) vs z Analysis (SymPy)"])
1619
 
1620
  # Tab 1: Eigenvalue Analysis (KEEP UNCHANGED from original)
1621
  with tab1:
@@ -1649,7 +1883,7 @@ with tab1:
1649
  max_value=500,
1650
  value=100,
1651
  step=10,
1652
- help="Number of points to calculate along the β axis (0 to 1)",
1653
  key="eig_fineness"
1654
  )
1655
  st.markdown('</div>', unsafe_allow_html=True)
@@ -1832,7 +2066,7 @@ with tab1:
1832
  color=color_max,
1833
  line=dict(color='white', width=1)
1834
  ),
1835
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
1836
  ))
1837
 
1838
  fig.add_trace(go.Scatter(
@@ -1847,7 +2081,7 @@ with tab1:
1847
  color=color_min,
1848
  line=dict(color='white', width=1)
1849
  ),
1850
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
1851
  ))
1852
 
1853
  fig.add_trace(go.Scatter(
@@ -1862,7 +2096,7 @@ with tab1:
1862
  color=color_theory_max,
1863
  line=dict(color='white', width=1)
1864
  ),
1865
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
1866
  ))
1867
 
1868
  fig.add_trace(go.Scatter(
@@ -1877,7 +2111,7 @@ with tab1:
1877
  color=color_theory_min,
1878
  line=dict(color='white', width=1)
1879
  ),
1880
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
1881
  ))
1882
 
1883
  # Configure layout for better appearance
@@ -1891,7 +2125,7 @@ with tab1:
1891
  'yanchor': 'top'
1892
  },
1893
  xaxis={
1894
- 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
1895
  'tickfont': {'size': 14},
1896
  'gridcolor': 'rgba(220, 220, 220, 0.5)',
1897
  'showgrid': True
@@ -1987,7 +2221,7 @@ with tab1:
1987
  color=color_max,
1988
  line=dict(color='white', width=1)
1989
  ),
1990
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
1991
  ))
1992
 
1993
  fig.add_trace(go.Scatter(
@@ -2002,7 +2236,7 @@ with tab1:
2002
  color=color_min,
2003
  line=dict(color='white', width=1)
2004
  ),
2005
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
2006
  ))
2007
 
2008
  fig.add_trace(go.Scatter(
@@ -2017,7 +2251,7 @@ with tab1:
2017
  color=color_theory_max,
2018
  line=dict(color='white', width=1)
2019
  ),
2020
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
2021
  ))
2022
 
2023
  fig.add_trace(go.Scatter(
@@ -2032,7 +2266,7 @@ with tab1:
2032
  color=color_theory_min,
2033
  line=dict(color='white', width=1)
2034
  ),
2035
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
2036
  ))
2037
 
2038
  # Configure layout for better appearance
@@ -2046,7 +2280,7 @@ with tab1:
2046
  'yanchor': 'top'
2047
  },
2048
  xaxis={
2049
- 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
2050
  'tickfont': {'size': 14},
2051
  'gridcolor': 'rgba(220, 220, 220, 0.5)',
2052
  'showgrid': True
@@ -2075,247 +2309,176 @@ with tab1:
2075
  st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
2076
 
2077
  except Exception as e:
2078
- st.info("👈 Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2079
  else:
2080
  # Show placeholder
2081
- st.info("👈 Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2082
 
2083
  st.markdown('</div>', unsafe_allow_html=True)
2084
 
2085
- # Tab 2: Im(s) vs z Analysis with SymPy
 
2086
  with tab2:
2087
- # Two-column layout
2088
- left_column, right_column = st.columns([1, 3])
2089
-
2090
- with left_column:
2091
- st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
2092
- st.markdown('<div class="panel-header">Im(s) vs z Analysis Controls</div>', unsafe_allow_html=True)
2093
-
2094
- # Parameter inputs with defaults and validation
2095
- st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
2096
- st.markdown("### Cubic Equation Parameters")
2097
- cubic_a = st.number_input("Value for a", min_value=1.1, max_value=1000.0, value=2.0, step=0.1,
2098
- help="Parameter a > 1", key="cubic_a")
2099
- cubic_y = st.number_input("Value for y", min_value=0.1, max_value=10.0, value=1.0, step=0.1,
2100
- help="Parameter y > 0", key="cubic_y")
2101
- cubic_beta = st.number_input("Value for β", min_value=0.0, max_value=1.0, value=0.5, step=0.05,
2102
- help="Value between 0 and 1", key="cubic_beta")
2103
- st.markdown('</div>', unsafe_allow_html=True)
2104
-
2105
- st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
2106
- st.markdown("### Z-Axis Range")
2107
- z_min = st.number_input("Z minimum", min_value=0.01, max_value=1.0, value=0.01, step=0.01,
2108
- help="Minimum z value for calculation", key="z_min")
2109
- z_max = st.number_input("Z maximum", min_value=1.0, max_value=100.0, value=10.0, step=1.0,
2110
- help="Maximum z value for calculation", key="z_max")
2111
- cubic_points = st.slider(
2112
- "Number of z points",
2113
- min_value=50,
2114
- max_value=1000,
2115
- value=300,
2116
- step=50,
2117
- help="Number of points to calculate along the z axis",
2118
- key="cubic_points"
2119
- )
2120
- st.markdown('</div>', unsafe_allow_html=True)
2121
-
2122
- # Show cubic equation
2123
- st.markdown('<div class="math-box">', unsafe_allow_html=True)
2124
- st.markdown("### Cubic Equation")
2125
- st.latex(r"zas^3 + [z(a+1)+a(1-y)]\,s^2 + [z+(a+1)-y-y\beta (a-1)]\,s + 1 = 0")
2126
- st.markdown('</div>', unsafe_allow_html=True)
2127
-
2128
- # Generate button
2129
- cubic_generate_button = st.button("Generate Im(s) vs z Analysis",
2130
- type="primary",
2131
- use_container_width=True,
2132
- key="cubic_generate")
2133
- st.markdown('</div>', unsafe_allow_html=True)
2134
-
2135
- with right_column:
2136
- # Main visualization area
2137
- st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
2138
- st.markdown('<div class="panel-header">Im(s) vs z Analysis Results</div>', unsafe_allow_html=True)
2139
-
2140
- # Container for the analysis results
2141
- cubic_results_container = st.container()
2142
-
2143
- # Process when generate button is clicked
2144
- if cubic_generate_button:
2145
- with cubic_results_container:
2146
- # Show progress
2147
- progress_container = st.container()
2148
- with progress_container:
2149
- progress_bar = st.progress(0)
2150
- status_text = st.empty()
2151
- status_text.text("Starting cubic equation calculations with SymPy...")
2152
-
2153
- try:
2154
- # Create data file path
2155
- data_file = os.path.join(output_dir, "cubic_data.json")
2156
-
2157
- # Run the Im(s) vs z analysis using Python SymPy with high precision
2158
- start_time = time.time()
2159
-
2160
- # Define progress callback for updating the progress bar
2161
- def update_progress(progress):
2162
- progress_bar.progress(progress)
2163
- status_text.text(f"Calculating with SymPy... {int(progress * 100)}% complete")
2164
-
2165
- # Run the analysis with progress updates
2166
- result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
2167
- end_time = time.time()
2168
-
2169
- # Format the data for saving
2170
- save_data = {
2171
- 'z_values': result['z_values'],
2172
- 'ims_values1': result['ims_values1'],
2173
- 'ims_values2': result['ims_values2'],
2174
- 'ims_values3': result['ims_values3'],
2175
- 'real_values1': result['real_values1'],
2176
- 'real_values2': result['real_values2'],
2177
- 'real_values3': result['real_values3'],
2178
- 'parameters': {'a': cubic_a, 'y': cubic_y, 'beta': cubic_beta}
2179
- }
2180
-
2181
- # Save results to JSON
2182
- save_as_json(save_data, data_file)
2183
- status_text.text("SymPy calculations complete! Generating visualization...")
2184
-
2185
- # Clear progress container
2186
- progress_container.empty()
2187
-
2188
- # Create Dash-style visualization
2189
- dash_fig = create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta)
2190
- st.plotly_chart(dash_fig, use_container_width=True)
2191
-
2192
- # Create sub-tabs for additional visualizations
2193
- pattern_tab, complex_tab = st.tabs(["Root Pattern Analysis", "Complex Plane View"])
2194
-
2195
- # Root pattern visualization
2196
- with pattern_tab:
2197
- pattern_fig = create_root_pattern_visualization(result)
2198
- st.plotly_chart(pattern_fig, use_container_width=True)
2199
-
2200
- # Root pattern explanation
2201
- st.markdown('<div class="explanation-box">', unsafe_allow_html=True)
2202
  st.markdown("""
2203
- ### Root Pattern Analysis
2204
-
2205
- The cubic equation in this analysis should ideally exhibit roots with the following pattern:
2206
-
2207
- - One root with negative real part
2208
- - One root with zero real part
2209
- - One root with positive real part
2210
-
2211
- Or, in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values.
2212
-
2213
- The SymPy implementation with high precision ensures accurate root-finding and pattern maintenance, which is essential for stability analysis.
2214
- Blue points indicate where the ideal pattern is achieved, green points show where all roots are zero, and red points indicate other patterns.
2215
  """)
2216
- st.markdown('</div>', unsafe_allow_html=True)
2217
-
2218
- # Complex plane visualization
2219
- with complex_tab:
2220
- # Slider for selecting z value
2221
- z_idx = st.slider(
2222
- "Select z index",
2223
- min_value=0,
2224
- max_value=len(result['z_values'])-1,
2225
- value=len(result['z_values'])//2,
2226
- help="Select a specific z value to visualize its roots in the complex plane"
2227
- )
2228
-
2229
- # Create complex plane visualization
2230
- complex_fig = create_complex_plane_visualization(result, z_idx)
2231
- st.plotly_chart(complex_fig, use_container_width=True)
2232
-
2233
- # Complex plane explanation
2234
- st.markdown('<div class="explanation-box">', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
2235
  st.markdown("""
2236
- ### Complex Plane Visualization
2237
-
2238
- This visualization shows the three roots of the cubic equation in the complex plane for the selected z value.
2239
- The real part is shown on the horizontal axis, and the imaginary part on the vertical axis.
2240
-
2241
- - The dashed circle represents the unit circle |s| = 1
2242
- - The roots are colored to match the plots above
2243
- - Conjugate pairs of roots (with opposite imaginary parts) often appear in cubic equations
2244
-
2245
- Use the slider to explore how the roots move in the complex plane as z changes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2246
  """)
2247
- st.markdown('</div>', unsafe_allow_html=True)
2248
-
2249
- # Display computation time
2250
- st.success(f"SymPy computation completed in {end_time - start_time:.2f} seconds")
2251
-
2252
- except Exception as e:
2253
- st.error(f"An error occurred: {str(e)}")
2254
- st.exception(e)
2255
-
2256
- else:
2257
- # Try to load existing data if available
2258
- data_file = os.path.join(output_dir, "cubic_data.json")
2259
- if os.path.exists(data_file):
2260
- try:
2261
- with open(data_file, 'r') as f:
2262
- data = json.load(f)
2263
-
2264
- # Process data safely and convert it to the format we need
2265
- result = {
2266
- 'z_values': np.array([safe_convert_to_numeric(x) for x in data['z_values']]),
2267
- 'ims_values1': np.array([safe_convert_to_numeric(x) for x in data['ims_values1']]),
2268
- 'ims_values2': np.array([safe_convert_to_numeric(x) for x in data['ims_values2']]),
2269
- 'ims_values3': np.array([safe_convert_to_numeric(x) for x in data['ims_values3']]),
2270
- 'real_values1': np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(data['z_values']))]),
2271
- 'real_values2': np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(data['z_values']))]),
2272
- 'real_values3': np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(data['z_values']))]),
2273
- }
2274
-
2275
- # Extract cubic parameters from data if available (otherwise use defaults)
2276
- cubic_params = data.get('parameters', {'a': 2.0, 'y': 1.0, 'beta': 0.5})
2277
- cubic_a = cubic_params.get('a', 2.0)
2278
- cubic_y = cubic_params.get('y', 1.0)
2279
- cubic_beta = cubic_params.get('beta', 0.5)
2280
-
2281
- # Create Dash-style visualization from previous data
2282
- st.info("Displaying previous analysis results. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
2283
-
2284
- dash_fig = create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta)
2285
- st.plotly_chart(dash_fig, use_container_width=True)
2286
-
2287
- # Create sub-tabs for additional visualizations
2288
- pattern_tab, complex_tab = st.tabs(["Root Pattern Analysis", "Complex Plane View"])
2289
-
2290
- # Root pattern visualization
2291
- with pattern_tab:
2292
- pattern_fig = create_root_pattern_visualization(result)
2293
- st.plotly_chart(pattern_fig, use_container_width=True)
2294
-
2295
- # Complex plane visualization
2296
- with complex_tab:
2297
- # Slider for selecting z value
2298
- z_idx = st.slider(
2299
- "Select z index",
2300
- min_value=0,
2301
- max_value=len(result['z_values'])-1,
2302
- value=len(result['z_values'])//2,
2303
- help="Select a specific z value to visualize its roots in the complex plane"
2304
- )
2305
-
2306
- # Create complex plane visualization
2307
- complex_fig = create_complex_plane_visualization(result, z_idx)
2308
- st.plotly_chart(complex_fig, use_container_width=True)
2309
-
2310
- except Exception as e:
2311
- st.info("👈 Set parameters and click 'Generate Im(s) vs z Analysis' to create a visualization.")
2312
- st.error(f"Error loading previous data: {str(e)}")
2313
- else:
2314
- # Show placeholder
2315
- st.info("👈 Set parameters and click 'Generate Im(s) vs z Analysis' to create a visualization.")
2316
-
2317
- st.markdown('</div>', unsafe_allow_html=True)
2318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2319
  # Add footer with instructions
2320
  st.markdown("""
2321
  <div class="footer">
 
12
  import sys
13
  import tempfile
14
  import platform
15
+ from sympy import symbols, solve, I, re, im, Poly, simplify, N
16
+ import mpmath
17
+ from scipy.stats import gaussian_kde
18
 
19
  # Set page config with wider layout
20
  st.set_page_config(
21
  page_title="Matrix Analysis Dashboard",
22
+ page_icon="chart",
23
  layout="wide",
24
  initial_sidebar_state="expanded"
25
  )
 
390
  }
391
 
392
  if (std::abs(delta0) < zero_threshold) {
393
+ // Delta0 0: One double root and one simple root
394
  double simple = std::cbrt(-delta1);
395
  double doubleRoot = -simple/2 - shift;
396
  double simpleRoot = simple - shift;
 
718
  << ", theory_tolerance = " << theory_tolerance << std::endl;
719
  std::cout << "Output will be saved to: " << output_file << std::endl;
720
 
721
+ // ─── Beta range parameters ────────────────────────────────────────
722
  const int num_beta_points = fineness; // Controlled by fineness parameter
723
  std::vector<double> beta_values(num_beta_points);
724
  for (int i = 0; i < num_beta_points; ++i) {
725
  beta_values[i] = static_cast<double>(i) / (num_beta_points - 1);
726
  }
727
 
728
+ // ─── Storage for results ────────────────────────────────────────
729
  std::vector<double> max_eigenvalues(num_beta_points);
730
  std::vector<double> min_eigenvalues(num_beta_points);
731
  std::vector<double> theoretical_max_values(num_beta_points);
732
  std::vector<double> theoretical_min_values(num_beta_points);
733
 
734
  try {
735
+ // ─── RandomGaussian X and S_n ────────────────────────────────
736
  std::random_device rd;
737
  std::mt19937_64 rng{rd()};
738
  std::normal_distribution<double> norm(0.0, 1.0);
 
742
  for(int j = 0; j < n; ++j)
743
  X.at<double>(i,j) = norm(rng);
744
 
745
+ // ─── Process each beta value ─────────────────────────────────
746
  for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) {
747
  double beta = beta_values[beta_idx];
748
 
 
750
  theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
751
  theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
752
 
753
+ // ─── Build T_n matrix ──────────────────────────────────
754
  int k = static_cast<int>(std::floor(beta * p));
755
  std::vector<double> diags(p, 1.0);
756
  std::fill_n(diags.begin(), k, a);
 
761
  T_n.at<double>(i,i) = diags[i];
762
  }
763
 
764
+ // ─── Form B_n = (1/n) * X * T_n * X^T ────────────
765
  cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
766
 
767
+ // ─── Compute eigenvalues of B ────────────────────────────
768
  cv::Mat eigVals;
769
  cv::eigen(B, eigVals);
770
  std::vector<double> eigs(n);
 
822
 
823
  try {
824
  if (mode == "eigenvalues") {
825
+ // ─── Eigenvalue analysis mode ───────────────────────────────────────────
826
  if (argc != 10) {
827
  std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl;
828
  std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
 
859
 
860
  # Compile the C++ code with the right OpenCV libraries
861
  st.sidebar.title("Dashboard Settings")
862
+ need_compile = not os.path.exists(executable) or st.sidebar.button("Recompile C++ Code")
863
 
864
  if need_compile:
865
  with st.sidebar:
866
  with st.spinner("Compiling C++ code..."):
867
  # Try to detect the OpenCV installation
868
+ opencv_detection_cmd = ["pkg-config", "--cflags", "--libs", "opencv4"]
 
869
  opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False)
870
 
871
  compile_commands = []
 
893
 
894
  if success:
895
  compiled = True
896
+ st.success(f"Successfully compiled with: {cmd}")
897
  break
898
 
899
  if not compiled:
900
+ st.error("All compilation attempts failed.")
901
  with st.expander("Compilation Details"):
902
  st.code(compile_output)
903
  st.stop()
 
906
  if platform.system() != "Windows":
907
  os.chmod(executable, 0o755)
908
 
909
+ st.success("C++ code compiled successfully!")
910
 
911
  # Set higher precision for mpmath
912
  mpmath.mp.dps = 100 # 100 digits of precision
 
1044
  progress_callback(i / num_points)
1045
 
1046
  # Coefficients for the cubic equation:
1047
+ # zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
1048
  coef_a = z * a
1049
  coef_b = z * (a + 1) + a * (1 - y)
1050
  coef_c = z + (a + 1) - y - y * beta * (a - 1)
 
1118
  rows=2,
1119
  cols=1,
1120
  subplot_titles=(
1121
+ f"Imaginary Parts of Roots: a={cubic_a}, y={cubic_y}, β={cubic_beta}",
1122
+ f"Real Parts of Roots: a={cubic_a}, y={cubic_y}, β={cubic_beta}"
1123
  ),
1124
  vertical_spacing=0.15,
1125
  specs=[[{"type": "scatter"}], [{"type": "scatter"}]]
 
1131
  x=z_values,
1132
  y=ims_values1,
1133
  mode='lines',
1134
+ name='Im(s)',
1135
  line=dict(color='rgb(239, 85, 59)', width=2.5),
1136
+ hovertemplate='z: %{x:.4f}<br>Im(s): %{y:.6f}<extra>Root 1</extra>'
1137
  ),
1138
  row=1, col=1
1139
  )
 
1143
  x=z_values,
1144
  y=ims_values2,
1145
  mode='lines',
1146
+ name='Im(s)',
1147
  line=dict(color='rgb(0, 129, 201)', width=2.5),
1148
+ hovertemplate='z: %{x:.4f}<br>Im(s): %{y:.6f}<extra>Root 2</extra>'
1149
  ),
1150
  row=1, col=1
1151
  )
 
1155
  x=z_values,
1156
  y=ims_values3,
1157
  mode='lines',
1158
+ name='Im(s)',
1159
  line=dict(color='rgb(0, 176, 80)', width=2.5),
1160
+ hovertemplate='z: %{x:.4f}<br>Im(s): %{y:.6f}<extra>Root 3</extra>'
1161
  ),
1162
  row=1, col=1
1163
  )
 
1168
  x=z_values,
1169
  y=real_values1,
1170
  mode='lines',
1171
+ name='Re(s)',
1172
  line=dict(color='rgb(239, 85, 59)', width=2.5),
1173
+ hovertemplate='z: %{x:.4f}<br>Re(s): %{y:.6f}<extra>Root 1</extra>'
1174
  ),
1175
  row=2, col=1
1176
  )
 
1180
  x=z_values,
1181
  y=real_values2,
1182
  mode='lines',
1183
+ name='Re(s)',
1184
  line=dict(color='rgb(0, 129, 201)', width=2.5),
1185
+ hovertemplate='z: %{x:.4f}<br>Re(s): %{y:.6f}<extra>Root 2</extra>'
1186
  ),
1187
  row=2, col=1
1188
  )
 
1192
  x=z_values,
1193
  y=real_values3,
1194
  mode='lines',
1195
+ name='Re(s)',
1196
  line=dict(color='rgb(0, 176, 80)', width=2.5),
1197
+ hovertemplate='z: %{x:.4f}<br>Re(s): %{y:.6f}<extra>Root 3</extra>'
1198
  ),
1199
  row=2, col=1
1200
  )
 
1484
  symbol='circle',
1485
  line=dict(width=1, color='black')
1486
  ),
1487
+ text=['s', 's', 's'],
1488
  textposition="top center",
1489
  name='Roots'
1490
  ))
 
1579
 
1580
  return fig
1581
 
1582
+ # ----- Additional Complex Root Utilities -----
1583
+ def compute_cubic_roots(z, beta, z_a, y):
1584
+ """Compute roots of the cubic equation using SymPy for high precision."""
1585
+ y_effective = y if y > 1 else 1 / y
1586
+ from sympy import symbols, solve, N, Poly
1587
+ s = symbols('s')
1588
+ a = z * z_a
1589
+ b = z * z_a + z + z_a - z_a * y_effective
1590
+ c = z + z_a + 1 - y_effective * (beta * z_a + 1 - beta)
1591
+ d = 1
1592
+ if abs(a) < 1e-10:
1593
+ if abs(b) < 1e-10:
1594
+ roots = np.array([-d / c, 0, 0], dtype=complex)
1595
+ else:
1596
+ quad_roots = np.roots([b, c, d])
1597
+ roots = np.append(quad_roots, 0).astype(complex)
1598
+ return roots
1599
+ try:
1600
+ cubic_eq = Poly(a * s ** 3 + b * s ** 2 + c * s + d, s)
1601
+ symbolic_roots = solve(cubic_eq, s)
1602
+ numerical_roots = [complex(N(root, 30)) for root in symbolic_roots]
1603
+ while len(numerical_roots) < 3:
1604
+ numerical_roots.append(0j)
1605
+ return np.array(numerical_roots, dtype=complex)
1606
+ except Exception:
1607
+ coeffs = [a, b, c, d]
1608
+ return np.roots(coeffs)
1609
+
1610
+
1611
+ def track_roots_consistently(z_values, all_roots):
1612
+ n_points = len(z_values)
1613
+ n_roots = all_roots[0].shape[0]
1614
+ tracked_roots = np.zeros((n_points, n_roots), dtype=complex)
1615
+ tracked_roots[0] = all_roots[0]
1616
+ for i in range(1, n_points):
1617
+ prev_roots = tracked_roots[i - 1]
1618
+ current_roots = all_roots[i]
1619
+ assigned = np.zeros(n_roots, dtype=bool)
1620
+ assignments = np.zeros(n_roots, dtype=int)
1621
+ for j in range(n_roots):
1622
+ distances = np.abs(current_roots - prev_roots[j])
1623
+ while True:
1624
+ best_idx = np.argmin(distances)
1625
+ if not assigned[best_idx]:
1626
+ assignments[j] = best_idx
1627
+ assigned[best_idx] = True
1628
+ break
1629
+ distances[best_idx] = np.inf
1630
+ if np.all(distances == np.inf):
1631
+ assignments[j] = j
1632
+ break
1633
+ tracked_roots[i] = current_roots[assignments]
1634
+ return tracked_roots
1635
+
1636
+
1637
+ def generate_cubic_discriminant(z, beta, z_a, y_effective):
1638
+ a = z * z_a
1639
+ b = z * z_a + z + z_a - z_a * y_effective
1640
+ c = z + z_a + 1 - y_effective * (beta * z_a + 1 - beta)
1641
+ d = 1
1642
+ return (18 * a * b * c * d - 27 * a ** 2 * d ** 2 + b ** 2 * c ** 2 -
1643
+ 2 * b ** 3 * d - 9 * a * c ** 3)
1644
+
1645
+
1646
+ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1647
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
1648
+ st.error("Invalid input parameters.")
1649
+ return None, None, None
1650
+ y_effective = y if y > 1 else 1 / y
1651
+ z_points = np.linspace(z_min, z_max, n_points)
1652
+ all_roots = []
1653
+ discriminants = []
1654
+ progress_bar = st.progress(0)
1655
+ status_text = st.empty()
1656
+ for i, z in enumerate(z_points):
1657
+ progress_bar.progress((i + 1) / n_points)
1658
+ status_text.text(f"Computing roots for z = {z:.3f} ({i+1}/{n_points})")
1659
+ roots = compute_cubic_roots(z, beta, z_a, y)
1660
+ roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1661
+ all_roots.append(roots)
1662
+ disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1663
+ discriminants.append(disc)
1664
+ progress_bar.empty()
1665
+ status_text.empty()
1666
+ all_roots = np.array(all_roots)
1667
+ discriminants = np.array(discriminants)
1668
+ tracked_roots = track_roots_consistently(z_points, all_roots)
1669
+ ims = np.imag(tracked_roots)
1670
+ res = np.real(tracked_roots)
1671
+ fig_im = go.Figure()
1672
+ for i in range(3):
1673
+ fig_im.add_trace(go.Scatter(x=z_points, y=ims[:, i], mode="lines", name=f"Im{{s{i+1}}}", line=dict(width=2)))
1674
+ disc_zeros = []
1675
+ for i in range(len(discriminants) - 1):
1676
+ if discriminants[i] * discriminants[i + 1] <= 0:
1677
+ zero_pos = z_points[i] + (z_points[i + 1] - z_points[i]) * (0 - discriminants[i]) / (discriminants[i + 1] - discriminants[i])
1678
+ disc_zeros.append(zero_pos)
1679
+ fig_im.add_vline(x=zero_pos, line=dict(color="red", width=1, dash="dash"))
1680
+ fig_im.update_layout(title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1681
+ xaxis_title="z", yaxis_title="Im{s}", hovermode="x unified")
1682
+ fig_re = go.Figure()
1683
+ for i in range(3):
1684
+ fig_re.add_trace(go.Scatter(x=z_points, y=res[:, i], mode="lines", name=f"Re{{s{i+1}}}", line=dict(width=2)))
1685
+ for zero_pos in disc_zeros:
1686
+ fig_re.add_vline(x=zero_pos, line=dict(color="red", width=1, dash="dash"))
1687
+ fig_re.update_layout(title=f"Re{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1688
+ xaxis_title="z", yaxis_title="Re{s}", hovermode="x unified")
1689
+ fig_disc = go.Figure()
1690
+ fig_disc.add_trace(go.Scatter(x=z_points, y=discriminants, mode="lines", name="Cubic Discriminant", line=dict(color="black", width=2)))
1691
+ fig_disc.add_hline(y=0, line=dict(color="red", width=1, dash="dash"))
1692
+ fig_disc.update_layout(title=f"Cubic Discriminant vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1693
+ xaxis_title="z", yaxis_title="Discriminant", hovermode="x unified")
1694
+ return fig_im, fig_re, fig_disc
1695
+
1696
+
1697
+ def analyze_complex_root_structure(beta_values, z, z_a, y):
1698
+ y_effective = y if y > 1 else 1 / y
1699
+ transition_points = []
1700
+ structure_types = []
1701
+ previous_type = None
1702
+ for beta in beta_values:
1703
+ roots = compute_cubic_roots(z, beta, z_a, y)
1704
+ is_all_real = all(abs(root.imag) < 1e-10 for root in roots)
1705
+ current_type = "real" if is_all_real else "complex"
1706
+ if previous_type is not None and current_type != previous_type:
1707
+ transition_points.append(beta)
1708
+ structure_types.append(previous_type)
1709
+ previous_type = current_type
1710
+ if previous_type is not None:
1711
+ structure_types.append(previous_type)
1712
+ return transition_points, structure_types
1713
+
1714
+
1715
+ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1716
+ if z_a <= 0 or y <= 0 or beta_min >= beta_max:
1717
+ st.error("Invalid input parameters.")
1718
+ return None, None, None
1719
+ y_effective = y if y > 1 else 1 / y
1720
+ beta_points = np.linspace(beta_min, beta_max, n_points)
1721
+ all_roots = []
1722
+ discriminants = []
1723
+ progress_bar = st.progress(0)
1724
+ status_text = st.empty()
1725
+ for i, beta in enumerate(beta_points):
1726
+ progress_bar.progress((i + 1) / n_points)
1727
+ status_text.text(f"Computing roots for β = {beta:.3f} ({i+1}/{n_points})")
1728
+ roots = compute_cubic_roots(z, beta, z_a, y)
1729
+ roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1730
+ all_roots.append(roots)
1731
+ disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1732
+ discriminants.append(disc)
1733
+ progress_bar.empty()
1734
+ status_text.empty()
1735
+ all_roots = np.array(all_roots)
1736
+ discriminants = np.array(discriminants)
1737
+ tracked_roots = track_roots_consistently(beta_points, all_roots)
1738
+ ims = np.imag(tracked_roots)
1739
+ res = np.real(tracked_roots)
1740
+ fig_im = go.Figure()
1741
+ for i in range(3):
1742
+ fig_im.add_trace(go.Scatter(x=beta_points, y=ims[:, i], mode="lines", name=f"Im{{s{i+1}}}", line=dict(width=2)))
1743
+ disc_zeros = []
1744
+ for i in range(len(discriminants) - 1):
1745
+ if discriminants[i] * discriminants[i + 1] <= 0:
1746
+ zero_pos = beta_points[i] + (beta_points[i + 1] - beta_points[i]) * (0 - discriminants[i]) / (discriminants[i + 1] - discriminants[i])
1747
+ disc_zeros.append(zero_pos)
1748
+ fig_im.add_vline(x=zero_pos, line=dict(color="red", width=1, dash="dash"))
1749
+ fig_im.update_layout(title=f"Im{{s}} vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1750
+ xaxis_title="β", yaxis_title="Im{s}", hovermode="x unified")
1751
+ fig_re = go.Figure()
1752
+ for i in range(3):
1753
+ fig_re.add_trace(go.Scatter(x=beta_points, y=res[:, i], mode="lines", name=f"Re{{s{i+1}}}", line=dict(width=2)))
1754
+ for zero_pos in disc_zeros:
1755
+ fig_re.add_vline(x=zero_pos, line=dict(color="red", width=1, dash="dash"))
1756
+ fig_re.update_layout(title=f"Re{{s}} vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1757
+ xaxis_title="β", yaxis_title="Re{s}", hovermode="x unified")
1758
+ fig_disc = go.Figure()
1759
+ fig_disc.add_trace(go.Scatter(x=beta_points, y=discriminants, mode="lines", name="Cubic Discriminant", line=dict(color="black", width=2)))
1760
+ fig_disc.add_hline(y=0, line=dict(color="red", width=1, dash="dash"))
1761
+ fig_disc.update_layout(title=f"Cubic Discriminant vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
1762
+ xaxis_title="β", yaxis_title="Discriminant", hovermode="x unified")
1763
+ return fig_im, fig_re, fig_disc
1764
+
1765
+
1766
+ def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_max=10.0, beta_steps=100, z_steps=100):
1767
+ y_effective = y if y > 1 else 1 / y
1768
+ beta_values = np.linspace(beta_min, beta_max, beta_steps)
1769
+ z_values = np.linspace(z_min, z_max, z_steps)
1770
+ phase_map = np.zeros((z_steps, beta_steps))
1771
+ progress_bar = st.progress(0)
1772
+ status_text = st.empty()
1773
+ for i, z in enumerate(z_values):
1774
+ progress_bar.progress((i + 1) / len(z_values))
1775
+ status_text.text(f"Analyzing phase at z = {z:.2f} ({i+1}/{len(z_values)})")
1776
+ for j, beta in enumerate(beta_values):
1777
+ roots = compute_cubic_roots(z, beta, z_a, y)
1778
+ is_all_real = all(abs(root.imag) < 1e-10 for root in roots)
1779
+ phase_map[i, j] = 1 if is_all_real else -1
1780
+ progress_bar.empty()
1781
+ status_text.empty()
1782
+ fig = go.Figure(data=go.Heatmap(z=phase_map, x=beta_values, y=z_values,
1783
+ colorscale=[[0, 'blue'], [0.5, 'white'], [1.0, 'red']],
1784
+ zmin=-1, zmax=1, showscale=True,
1785
+ colorbar=dict(title="Root Type", tickvals=[-1, 1], ticktext=["Complex Roots", "All Real Roots"])) )
1786
+ fig.update_layout(title=f"Phase Diagram: Root Structure (y={y:.3f}, z_a={z_a:.3f})",
1787
+ xaxis_title="β", yaxis_title="z", hovermode="closest")
1788
+ return fig
1789
+
1790
+
1791
+ @st.cache_data
1792
+ def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
1793
+ y_effective = y if y > 1 else 1 / y
1794
+ np.random.seed(seed)
1795
+ p = int(y_effective * n)
1796
+ k = int(np.floor(beta * p))
1797
+ diag_entries = np.concatenate([np.full(k, z_a), np.full(p - k, 1.0)])
1798
+ np.random.shuffle(diag_entries)
1799
+ T_n = np.diag(diag_entries)
1800
+ X = np.random.randn(p, n)
1801
+ S_n = (1 / n) * (X @ X.T)
1802
+ B_n = S_n @ T_n
1803
+ eigenvalues = np.linalg.eigvalsh(B_n)
1804
+ kde = gaussian_kde(eigenvalues)
1805
+ x_vals = np.linspace(min(eigenvalues), max(eigenvalues), 500)
1806
+ kde_vals = kde(x_vals)
1807
+ fig = go.Figure()
1808
+ fig.add_trace(go.Histogram(x=eigenvalues, histnorm='probability density', name="Histogram", marker=dict(color='blue', opacity=0.6)))
1809
+ fig.add_trace(go.Scatter(x=x_vals, y=kde_vals, mode="lines", name="KDE", line=dict(color='red', width=2)))
1810
+ fig.update_layout(title=f"Eigenvalue Distribution for B_n = S_n T_n (y={y:.1f}, β={beta:.2f}, a={z_a:.1f})",
1811
+ xaxis_title="Eigenvalue", yaxis_title="Density", hovermode="closest", showlegend=True)
1812
+ return fig, eigenvalues
1813
  # Options for theme and appearance
1814
+ def compute_eigenvalue_support_boundaries(z_a, y, betas, n_samples=1000, seeds=5):
1815
+ return np.zeros(len(betas)), np.ones(len(betas))
1816
  with st.sidebar.expander("Theme & Appearance"):
1817
  show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots")
1818
  color_theme = st.selectbox(
 
1849
  color_theory_min = 'rgb(180, 30, 180)'
1850
 
1851
  # Create tabs for different analyses
1852
+ tab1, tab2 = st.tabs(["Eigenvalue Analysis (C++)", "Im(s) vs z Analysis (SymPy)"])
1853
 
1854
  # Tab 1: Eigenvalue Analysis (KEEP UNCHANGED from original)
1855
  with tab1:
 
1883
  max_value=500,
1884
  value=100,
1885
  step=10,
1886
+ help="Number of points to calculate along the β axis (0 to 1)",
1887
  key="eig_fineness"
1888
  )
1889
  st.markdown('</div>', unsafe_allow_html=True)
 
2066
  color=color_max,
2067
  line=dict(color='white', width=1)
2068
  ),
2069
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
2070
  ))
2071
 
2072
  fig.add_trace(go.Scatter(
 
2081
  color=color_min,
2082
  line=dict(color='white', width=1)
2083
  ),
2084
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
2085
  ))
2086
 
2087
  fig.add_trace(go.Scatter(
 
2096
  color=color_theory_max,
2097
  line=dict(color='white', width=1)
2098
  ),
2099
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
2100
  ))
2101
 
2102
  fig.add_trace(go.Scatter(
 
2111
  color=color_theory_min,
2112
  line=dict(color='white', width=1)
2113
  ),
2114
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
2115
  ))
2116
 
2117
  # Configure layout for better appearance
 
2125
  'yanchor': 'top'
2126
  },
2127
  xaxis={
2128
+ 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
2129
  'tickfont': {'size': 14},
2130
  'gridcolor': 'rgba(220, 220, 220, 0.5)',
2131
  'showgrid': True
 
2221
  color=color_max,
2222
  line=dict(color='white', width=1)
2223
  ),
2224
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
2225
  ))
2226
 
2227
  fig.add_trace(go.Scatter(
 
2236
  color=color_min,
2237
  line=dict(color='white', width=1)
2238
  ),
2239
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
2240
  ))
2241
 
2242
  fig.add_trace(go.Scatter(
 
2251
  color=color_theory_max,
2252
  line=dict(color='white', width=1)
2253
  ),
2254
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
2255
  ))
2256
 
2257
  fig.add_trace(go.Scatter(
 
2266
  color=color_theory_min,
2267
  line=dict(color='white', width=1)
2268
  ),
2269
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
2270
  ))
2271
 
2272
  # Configure layout for better appearance
 
2280
  'yanchor': 'top'
2281
  },
2282
  xaxis={
2283
+ 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
2284
  'tickfont': {'size': 14},
2285
  'gridcolor': 'rgba(220, 220, 220, 0.5)',
2286
  'showgrid': True
 
2309
  st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
2310
 
2311
  except Exception as e:
2312
+ st.info("Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2313
  else:
2314
  # Show placeholder
2315
+ st.info("Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2316
 
2317
  st.markdown('</div>', unsafe_allow_html=True)
2318
 
2319
+
2320
+ # ----- Tab 2: Complex Root Analysis -----
2321
  with tab2:
2322
+ st.header("Complex Root Analysis")
2323
+ plot_tabs = st.tabs(["Im{s} vs. z", "Im{s} vs. β", "Phase Diagram", "Eigenvalue Distribution"])
2324
+
2325
+ with plot_tabs[0]:
2326
+ col1, col2 = st.columns([1, 2])
2327
+ with col1:
2328
+ beta_z = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0, key="beta_tab2_z")
2329
+ y_z = st.number_input("y", value=1.0, key="y_tab2_z")
2330
+ z_a_z = st.number_input("z_a", value=1.0, key="z_a_tab2_z")
2331
+ z_min_z = st.number_input("z_min", value=-10.0, key="z_min_tab2_z")
2332
+ z_max_z = st.number_input("z_max", value=10.0, key="z_max_tab2_z")
2333
+ with st.expander("Resolution Settings", expanded=False):
2334
+ z_points = st.slider("z grid points", min_value=100, max_value=2000, value=500, step=100, key="z_points_z")
2335
+ if st.button("Compute Complex Roots vs. z", key="tab2_button_z"):
2336
+ with col2:
2337
+ fig_im, fig_re, fig_disc = generate_root_plots(beta_z, y_z, z_a_z, z_min_z, z_max_z, z_points)
2338
+ if fig_im is not None and fig_re is not None and fig_disc is not None:
2339
+ st.plotly_chart(fig_im, use_container_width=True)
2340
+ st.plotly_chart(fig_re, use_container_width=True)
2341
+ st.plotly_chart(fig_disc, use_container_width=True)
2342
+ with st.expander("Root Structure Analysis", expanded=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2343
  st.markdown("""
2344
+ ### Root Structure Explanation
2345
+
2346
+ The red dashed vertical lines mark the points where the cubic discriminant equals zero.
2347
+ At these points, the cubic equation's root structure changes:
2348
+
2349
+ - When the discriminant is positive, the cubic has three distinct real roots.
2350
+ - When the discriminant is negative, the cubic has one real root and two complex conjugate roots.
2351
+ - When the discriminant is exactly zero, the cubic has at least two equal roots.
2352
+
2353
+ These transition points align perfectly with the z*(β) boundary curves from the first tab,
2354
+ which represent exactly these transitions in the (β,z) plane.
 
2355
  """)
2356
+
2357
+ with plot_tabs[1]:
2358
+ col1, col2 = st.columns([1, 2])
2359
+ with col1:
2360
+ z_beta = st.number_input("z", value=1.0, key="z_tab2_beta")
2361
+ y_beta = st.number_input("y", value=1.0, key="y_tab2_beta")
2362
+ z_a_beta = st.number_input("z_a", value=1.0, key="z_a_tab2_beta")
2363
+ beta_min = st.number_input("β_min", value=0.0, min_value=0.0, max_value=1.0, key="beta_min_tab2")
2364
+ beta_max = st.number_input("β_max", value=1.0, min_value=0.0, max_value=1.0, key="beta_max_tab2")
2365
+ with st.expander("Resolution Settings", expanded=False):
2366
+ beta_points = st.slider("β grid points", min_value=100, max_value=1000, value=500, step=100, key="beta_points")
2367
+ if st.button("Compute Complex Roots vs. β", key="tab2_button_beta"):
2368
+ with col2:
2369
+ fig_im_beta, fig_re_beta, fig_disc = generate_roots_vs_beta_plots(z_beta, y_beta, z_a_beta, beta_min, beta_max, beta_points)
2370
+ if fig_im_beta is not None and fig_re_beta is not None and fig_disc is not None:
2371
+ st.plotly_chart(fig_im_beta, use_container_width=True)
2372
+ st.plotly_chart(fig_re_beta, use_container_width=True)
2373
+ st.plotly_chart(fig_disc, use_container_width=True)
2374
+ transition_points, structure_types = analyze_complex_root_structure(np.linspace(beta_min, beta_max, beta_points), z_beta, z_a_beta, y_beta)
2375
+ if transition_points:
2376
+ st.subheader("Root Structure Transition Points")
2377
+ for i, beta in enumerate(transition_points):
2378
+ prev_type = structure_types[i]
2379
+ next_type = structure_types[i+1] if i+1 < len(structure_types) else "unknown"
2380
+ st.markdown(f"- At β = {beta:.6f}: Transition from {prev_type} roots to {next_type} roots")
2381
+ else:
2382
+ st.info("No transitions detected in root structure across this β range.")
2383
+ with st.expander("Analysis Explanation", expanded=False):
2384
  st.markdown("""
2385
+ ### Interpreting the Plots
2386
+
2387
+ - **Im{s} vs. β**: Shows how the imaginary parts of the roots change with β. When all curves are at Im{s}=0, all roots are real.
2388
+ - **Re{s} vs. β**: Shows how the real parts of the roots change with β.
2389
+ - **Discriminant Plot**: The cubic discriminant changes sign at points where the root structure changes.
2390
+ - When discriminant < 0: The cubic has one real root and two complex conjugate roots.
2391
+ - When discriminant > 0: The cubic has three distinct real roots.
2392
+ - When discriminant = 0: The cubic has multiple roots (at least two roots are equal).
2393
+
2394
+ The vertical red dashed lines mark the transition points where the root structure changes.
2395
+ """)
2396
+
2397
+ with plot_tabs[2]:
2398
+ col1, col2 = st.columns([1, 2])
2399
+ with col1:
2400
+ z_a_phase = st.number_input("z_a", value=1.0, key="z_a_phase")
2401
+ y_phase = st.number_input("y", value=1.0, key="y_phase")
2402
+ beta_min_phase = st.number_input("β_min", value=0.0, min_value=0.0, max_value=1.0, key="beta_min_phase")
2403
+ beta_max_phase = st.number_input("β_max", value=1.0, min_value=0.0, max_value=1.0, key="beta_max_phase")
2404
+ z_min_phase = st.number_input("z_min", value=-10.0, key="z_min_phase")
2405
+ z_max_phase = st.number_input("z_max", value=10.0, key="z_max_phase")
2406
+ with st.expander("Resolution Settings", expanded=False):
2407
+ beta_steps_phase = st.slider("β grid points", min_value=20, max_value=200, value=100, step=20, key="beta_steps_phase")
2408
+ z_steps_phase = st.slider("z grid points", min_value=20, max_value=200, value=100, step=20, key="z_steps_phase")
2409
+ if st.button("Generate Phase Diagram", key="tab2_button_phase"):
2410
+ with col2:
2411
+ st.info("Generating phase diagram. This may take a while depending on resolution...")
2412
+ fig_phase = generate_phase_diagram(z_a_phase, y_phase, beta_min_phase, beta_max_phase, z_min_phase, z_max_phase, beta_steps_phase, z_steps_phase)
2413
+ if fig_phase is not None:
2414
+ st.plotly_chart(fig_phase, use_container_width=True)
2415
+ with st.expander("Phase Diagram Explanation", expanded=False):
2416
+ st.markdown("""
2417
+ ### Understanding the Phase Diagram
2418
+
2419
+ This heatmap shows the regions in the (β, z) plane where:
2420
+
2421
+ - **Red Regions**: The cubic equation has all real roots
2422
+ - **Blue Regions**: The cubic equation has one real root and two complex conjugate roots
2423
+
2424
+ The boundaries between these regions represent values where the discriminant is zero,
2425
+ which are the exact same curves as the z*(β) boundaries in the first tab. This phase
2426
+ diagram provides a comprehensive view of the eigenvalue support structure.
2427
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2428
 
2429
+ with plot_tabs[3]:
2430
+ st.subheader("Eigenvalue Distribution for B_n = S_n T_n")
2431
+ with st.expander("Simulation Information", expanded=False):
2432
+ st.markdown("""
2433
+ This simulation generates the eigenvalue distribution of B_n as n→∞, where:
2434
+ - B_n = (1/n)XX^T with X being a p×n matrix
2435
+ - p/n → y as n→∞
2436
+ - The diagonal entries of T_n follow distribution β·δ(z_a) + (1-β)·δ(1)
2437
+ """)
2438
+ col_eigen1, col_eigen2 = st.columns([1, 2])
2439
+ with col_eigen1:
2440
+ beta_eigen = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0, key="beta_eigen")
2441
+ y_eigen = st.number_input("y", value=1.0, key="y_eigen")
2442
+ z_a_eigen = st.number_input("z_a", value=1.0, key="z_a_eigen")
2443
+ n_samples = st.slider("Number of samples (n)", min_value=100, max_value=2000, value=1000, step=100)
2444
+ sim_seed = st.number_input("Random seed", min_value=1, max_value=1000, value=42, step=1)
2445
+ show_theoretical = st.checkbox("Show theoretical boundaries", value=True)
2446
+ show_empirical_stats = st.checkbox("Show empirical statistics", value=True)
2447
+ if st.button("Generate Eigenvalue Distribution", key="tab2_eigen_button"):
2448
+ with col_eigen2:
2449
+ fig_eigen, eigenvalues = generate_eigenvalue_distribution(beta_eigen, y_eigen, z_a_eigen, n=n_samples, seed=sim_seed)
2450
+ if show_theoretical:
2451
+ betas = np.array([beta_eigen])
2452
+ min_eig, max_eig = compute_eigenvalue_support_boundaries(z_a_eigen, y_eigen, betas, n_samples=n_samples, seeds=5)
2453
+ fig_eigen.add_vline(x=min_eig[0], line=dict(color="red", width=2, dash="dash"), annotation_text="Min theoretical", annotation_position="top right")
2454
+ fig_eigen.add_vline(x=max_eig[0], line=dict(color="red", width=2, dash="dash"), annotation_text="Max theoretical", annotation_position="top left")
2455
+ st.plotly_chart(fig_eigen, use_container_width=True)
2456
+ if show_theoretical and show_empirical_stats:
2457
+ empirical_min = eigenvalues.min()
2458
+ empirical_max = eigenvalues.max()
2459
+ st.markdown("### Comparison of Empirical vs Theoretical Bounds")
2460
+ col1, col2, col3 = st.columns(3)
2461
+ with col1:
2462
+ st.metric("Theoretical Min", f"{min_eig[0]:.4f}")
2463
+ st.metric("Theoretical Max", f"{max_eig[0]:.4f}")
2464
+ st.metric("Theoretical Width", f"{max_eig[0] - min_eig[0]:.4f}")
2465
+ with col2:
2466
+ st.metric("Empirical Min", f"{empirical_min:.4f}")
2467
+ st.metric("Empirical Max", f"{empirical_max:.4f}")
2468
+ st.metric("Empirical Width", f"{empirical_max - empirical_min:.4f}")
2469
+ with col3:
2470
+ st.metric("Min Difference", f"{empirical_min - min_eig[0]:.4f}")
2471
+ st.metric("Max Difference", f"{empirical_max - max_eig[0]:.4f}")
2472
+ st.metric("Width Difference", f"{(empirical_max - empirical_min) - (max_eig[0] - min_eig[0]):.4f}")
2473
+ if show_empirical_stats:
2474
+ st.markdown("### Eigenvalue Statistics")
2475
+ col1, col2 = st.columns(2)
2476
+ with col1:
2477
+ st.metric("Mean", f"{np.mean(eigenvalues):.4f}")
2478
+ st.metric("Median", f"{np.median(eigenvalues):.4f}")
2479
+ with col2:
2480
+ st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}")
2481
+ st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}")
2482
  # Add footer with instructions
2483
  st.markdown("""
2484
  <div class="footer">