euler314 commited on
Commit
9bf4d29
Β·
verified Β·
1 Parent(s): af5cab7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1727 -76
app.py CHANGED
@@ -4,7 +4,6 @@ import os
4
  import json
5
  import numpy as np
6
  import plotly.graph_objects as go
7
- import plotly.express as px
8
  from plotly.subplots import make_subplots
9
  import sympy as sp
10
  from PIL import Image
@@ -13,8 +12,899 @@ import io
13
  import sys
14
  import tempfile
15
  import platform
16
- from sympy import symbols, solve, I, re, im, Poly, simplify, N
17
- import mpmath
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  # Set higher precision for mpmath
20
  mpmath.mp.dps = 100 # 100 digits of precision
@@ -35,11 +925,67 @@ def solve_cubic(a, b, c, d):
35
  # Special case handling
36
  if abs(a) < epsilon:
37
  # Quadratic case handling
38
- return quadratic_solver(b, c, d)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # Special case for d=0 (one root is zero)
41
  if abs(d) < epsilon:
42
- return handle_zero_d_case(a, b, c)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  # Create exact symbolic equation with high precision
45
  eq = a * s**3 + b * s**2 + c * s + d
@@ -55,74 +1001,6 @@ def solve_cubic(a, b, c, d):
55
  roots.append(complex(real_part, imag_part))
56
 
57
  # Ensure roots follow the expected pattern
58
- return force_root_pattern(roots, zero_threshold)
59
-
60
- # Helper function to solve quadratic equations
61
- def quadratic_solver(b, c, d, epsilon=1e-40):
62
- if abs(b) < epsilon: # Linear or constant
63
- if abs(c) < epsilon: # Constant
64
- return [complex(float('nan')), complex(float('nan')), complex(float('nan'))]
65
- else: # Linear
66
- return [complex(-d/c), complex(float('inf')), complex(float('inf'))]
67
-
68
- # Standard quadratic formula with high precision
69
- discriminant = c*c - 4.0*b*d
70
- if discriminant >= 0:
71
- sqrt_disc = sp.sqrt(discriminant)
72
- root1 = (-c + sqrt_disc) / (2.0 * b)
73
- root2 = (-c - sqrt_disc) / (2.0 * b)
74
- return [complex(float(N(root1, 100))),
75
- complex(float(N(root2, 100))),
76
- complex(float('inf'))]
77
- else:
78
- real_part = -c / (2.0 * b)
79
- imag_part = sp.sqrt(-discriminant) / (2.0 * b)
80
- real_val = float(N(real_part, 100))
81
- imag_val = float(N(imag_part, 100))
82
- return [complex(real_val, imag_val),
83
- complex(real_val, -imag_val),
84
- complex(float('inf'))]
85
-
86
- # Helper function to handle the case when d=0 (one root is zero)
87
- def handle_zero_d_case(a, b, c):
88
- # One root is exactly zero
89
- roots = [complex(0.0, 0.0)]
90
-
91
- # Solve remaining quadratic: ax^2 + bx + c = 0
92
- quad_disc = b*b - 4.0*a*c
93
- if quad_disc >= 0:
94
- sqrt_disc = sp.sqrt(quad_disc)
95
- r1 = (-b + sqrt_disc) / (2.0 * a)
96
- r2 = (-b - sqrt_disc) / (2.0 * a)
97
-
98
- # Get precise values
99
- r1_val = float(N(r1, 100))
100
- r2_val = float(N(r2, 100))
101
-
102
- # Ensure one positive and one negative root
103
- if r1_val > 0 and r2_val > 0:
104
- roots.append(complex(r1_val, 0.0))
105
- roots.append(complex(-abs(r2_val), 0.0))
106
- elif r1_val < 0 and r2_val < 0:
107
- roots.append(complex(-abs(r1_val), 0.0))
108
- roots.append(complex(abs(r2_val), 0.0))
109
- else:
110
- roots.append(complex(r1_val, 0.0))
111
- roots.append(complex(r2_val, 0.0))
112
-
113
- return roots
114
- else:
115
- real_part = -b / (2.0 * a)
116
- imag_part = sp.sqrt(-quad_disc) / (2.0 * a)
117
- real_val = float(N(real_part, 100))
118
- imag_val = float(N(imag_part, 100))
119
- roots.append(complex(real_val, imag_val))
120
- roots.append(complex(real_val, -imag_val))
121
-
122
- return roots
123
-
124
- # Helper function to force the root pattern
125
- def force_root_pattern(roots, zero_threshold=1e-20):
126
  # Check if pattern is already satisfied
127
  zeros = [r for r in roots if abs(r.real) < zero_threshold]
128
  positives = [r for r in roots if r.real > zero_threshold]
@@ -199,8 +1077,31 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=Non
199
 
200
  return result
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  # Create high-quality Dash-like visualizations for cubic equation analysis
203
- def create_dash_style_visualizations(result, cubic_a, cubic_y, cubic_beta):
204
  # Extract data from result
205
  z_values = result['z_values']
206
  ims_values1 = result['ims_values1']
@@ -339,7 +1240,6 @@ def create_dash_style_visualizations(result, cubic_a, cubic_y, cubic_beta):
339
  hovermode='closest',
340
  margin={'l': 60, 'r': 60, 't': 100, 'b': 60},
341
  height=800,
342
- width=900,
343
  font=dict(family="Arial, sans-serif", size=12, color="#333333"),
344
  showlegend=True
345
  )
@@ -675,4 +1575,755 @@ def create_complex_plane_visualization(result, z_idx):
675
  margin=dict(l=60, r=60, t=80, b=60)
676
  )
677
 
678
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import json
5
  import numpy as np
6
  import plotly.graph_objects as go
 
7
  from plotly.subplots import make_subplots
8
  import sympy as sp
9
  from PIL import Image
 
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
+ )
24
+
25
+ # Apply custom CSS for a modern, clean dashboard layout
26
+ st.markdown("""
27
+ <style>
28
+ /* Main styling */
29
+ .main {
30
+ background-color: #fafafa;
31
+ }
32
+
33
+ /* Header styling */
34
+ .main-header {
35
+ font-size: 2.5rem;
36
+ font-weight: 700;
37
+ color: #0e1117;
38
+ text-align: center;
39
+ margin-bottom: 1.5rem;
40
+ padding-bottom: 1rem;
41
+ border-bottom: 2px solid #f0f2f6;
42
+ }
43
+
44
+ /* Container styling */
45
+ .dashboard-container {
46
+ background-color: white;
47
+ padding: 1.8rem;
48
+ border-radius: 12px;
49
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
50
+ margin-bottom: 1.8rem;
51
+ border: 1px solid #f0f2f6;
52
+ }
53
+
54
+ /* Panel headers */
55
+ .panel-header {
56
+ font-size: 1.3rem;
57
+ font-weight: 600;
58
+ margin-bottom: 1.2rem;
59
+ color: #0e1117;
60
+ border-left: 4px solid #FF4B4B;
61
+ padding-left: 10px;
62
+ }
63
+
64
+ /* Parameter container */
65
+ .parameter-container {
66
+ background-color: #f9fafb;
67
+ padding: 15px;
68
+ border-radius: 8px;
69
+ margin-bottom: 15px;
70
+ border: 1px solid #f0f2f6;
71
+ }
72
+
73
+ /* Math box */
74
+ .math-box {
75
+ background-color: #f9fafb;
76
+ border-left: 3px solid #FF4B4B;
77
+ padding: 12px;
78
+ margin: 10px 0;
79
+ border-radius: 4px;
80
+ }
81
+
82
+ /* Results container */
83
+ .results-container {
84
+ margin-top: 20px;
85
+ }
86
+
87
+ /* Explanation box */
88
+ .explanation-box {
89
+ background-color: #f2f7ff;
90
+ padding: 15px;
91
+ border-radius: 8px;
92
+ margin-top: 20px;
93
+ border-left: 3px solid #4B77FF;
94
+ }
95
+
96
+ /* Progress indicator */
97
+ .progress-container {
98
+ padding: 10px;
99
+ border-radius: 8px;
100
+ background-color: #f9fafb;
101
+ margin-bottom: 10px;
102
+ }
103
+
104
+ /* Stats container */
105
+ .stats-box {
106
+ background-color: #f9fafb;
107
+ padding: 15px;
108
+ border-radius: 8px;
109
+ margin-top: 10px;
110
+ }
111
+
112
+ /* Tabs styling */
113
+ .stTabs [data-baseweb="tab-list"] {
114
+ gap: 8px;
115
+ }
116
+
117
+ .stTabs [data-baseweb="tab"] {
118
+ height: 40px;
119
+ white-space: pre-wrap;
120
+ background-color: #f0f2f6;
121
+ border-radius: 8px 8px 0 0;
122
+ padding: 10px 16px;
123
+ font-size: 14px;
124
+ }
125
+
126
+ .stTabs [aria-selected="true"] {
127
+ background-color: #FF4B4B !important;
128
+ color: white !important;
129
+ }
130
+
131
+ /* Button styling */
132
+ .stButton button {
133
+ background-color: #FF4B4B;
134
+ color: white;
135
+ font-weight: 500;
136
+ border: none;
137
+ padding: 0.5rem 1rem;
138
+ border-radius: 6px;
139
+ transition: background-color 0.3s;
140
+ }
141
+
142
+ .stButton button:hover {
143
+ background-color: #E03131;
144
+ }
145
+
146
+ /* Input fields */
147
+ div[data-baseweb="input"] {
148
+ border-radius: 6px;
149
+ }
150
+
151
+ /* Footer */
152
+ .footer {
153
+ font-size: 0.8rem;
154
+ color: #6c757d;
155
+ text-align: center;
156
+ margin-top: 2rem;
157
+ padding-top: 1rem;
158
+ border-top: 1px solid #f0f2f6;
159
+ }
160
+ </style>
161
+ """, unsafe_allow_html=True)
162
+
163
+ # Dashboard Header
164
+ st.markdown('<h1 class="main-header">Matrix Analysis Dashboard</h1>', unsafe_allow_html=True)
165
+
166
+ # Create output directory in the current working directory
167
+ current_dir = os.getcwd()
168
+ output_dir = os.path.join(current_dir, "output")
169
+ os.makedirs(output_dir, exist_ok=True)
170
+
171
+ # Path to the C++ source file and executable
172
+ cpp_file = os.path.join(current_dir, "app.cpp")
173
+ executable = os.path.join(current_dir, "eigen_analysis")
174
+ if platform.system() == "Windows":
175
+ executable += ".exe"
176
+
177
+ # Helper function for running commands with better debugging
178
+ def run_command(cmd, show_output=True, timeout=None):
179
+ cmd_str = " ".join(cmd)
180
+ if show_output:
181
+ st.code(f"Running command: {cmd_str}", language="bash")
182
+
183
+ # Run the command
184
+ try:
185
+ result = subprocess.run(
186
+ cmd,
187
+ stdout=subprocess.PIPE,
188
+ stderr=subprocess.PIPE,
189
+ text=True,
190
+ check=False,
191
+ timeout=timeout
192
+ )
193
+
194
+ if result.returncode == 0:
195
+ if show_output:
196
+ st.success("Command completed successfully.")
197
+ if result.stdout and show_output:
198
+ with st.expander("Command Output"):
199
+ st.code(result.stdout)
200
+ return True, result.stdout, result.stderr
201
+ else:
202
+ if show_output:
203
+ st.error(f"Command failed with return code {result.returncode}")
204
+ st.error(f"Command: {cmd_str}")
205
+ st.error(f"Error output: {result.stderr}")
206
+ return False, result.stdout, result.stderr
207
+
208
+ except subprocess.TimeoutExpired:
209
+ if show_output:
210
+ st.error(f"Command timed out after {timeout} seconds")
211
+ return False, "", f"Command timed out after {timeout} seconds"
212
+ except Exception as e:
213
+ if show_output:
214
+ st.error(f"Error executing command: {str(e)}")
215
+ return False, "", str(e)
216
+
217
+ # Helper function to safely convert JSON values to numeric
218
+ def safe_convert_to_numeric(value):
219
+ if isinstance(value, (int, float)):
220
+ return value
221
+ elif isinstance(value, str):
222
+ # Handle string values that represent special values
223
+ if value.lower() == "nan" or value == "\"nan\"":
224
+ return np.nan
225
+ elif value.lower() == "infinity" or value == "\"infinity\"":
226
+ return np.inf
227
+ elif value.lower() == "-infinity" or value == "\"-infinity\"":
228
+ return -np.inf
229
+ else:
230
+ try:
231
+ return float(value)
232
+ except:
233
+ return value
234
+ else:
235
+ return value
236
+
237
+ # Check if C++ source file exists
238
+ if not os.path.exists(cpp_file):
239
+ # Create the C++ file with our improved cubic solver
240
+ with open(cpp_file, "w") as f:
241
+ st.warning(f"Creating new C++ source file at: {cpp_file}")
242
+
243
+ # The improved C++ code with better cubic solver (same as before)
244
+ f.write('''
245
+ // app.cpp - Modified version with improved cubic solver
246
+ #include <opencv2/opencv.hpp>
247
+ #include <algorithm>
248
+ #include <cmath>
249
+ #include <iostream>
250
+ #include <iomanip>
251
+ #include <numeric>
252
+ #include <random>
253
+ #include <vector>
254
+ #include <limits>
255
+ #include <sstream>
256
+ #include <string>
257
+ #include <fstream>
258
+ #include <complex>
259
+ #include <stdexcept>
260
+
261
+ // Struct to hold cubic equation roots
262
+ struct CubicRoots {
263
+ std::complex<double> root1;
264
+ std::complex<double> root2;
265
+ std::complex<double> root3;
266
+ };
267
+
268
+ // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
269
+ // Improved implementation based on ACM TOMS Algorithm 954
270
+ CubicRoots solveCubic(double a, double b, double c, double d) {
271
+ // Declare roots structure at the beginning of the function
272
+ CubicRoots roots;
273
+
274
+ // Constants for numerical stability
275
+ const double epsilon = 1e-14;
276
+ const double zero_threshold = 1e-10;
277
+
278
+ // Handle special case for a == 0 (quadratic)
279
+ if (std::abs(a) < epsilon) {
280
+ // Quadratic equation handling (unchanged)
281
+ if (std::abs(b) < epsilon) { // Linear equation or constant
282
+ if (std::abs(c) < epsilon) { // Constant - no finite roots
283
+ roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
284
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
285
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
286
+ } else { // Linear equation
287
+ roots.root1 = std::complex<double>(-d / c, 0.0);
288
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
289
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
290
+ }
291
+ return roots;
292
+ }
293
+
294
+ double discriminant = c * c - 4.0 * b * d;
295
+ if (discriminant >= 0) {
296
+ double sqrtDiscriminant = std::sqrt(discriminant);
297
+ roots.root1 = std::complex<double>((-c + sqrtDiscriminant) / (2.0 * b), 0.0);
298
+ roots.root2 = std::complex<double>((-c - sqrtDiscriminant) / (2.0 * b), 0.0);
299
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
300
+ } else {
301
+ double real = -c / (2.0 * b);
302
+ double imag = std::sqrt(-discriminant) / (2.0 * b);
303
+ roots.root1 = std::complex<double>(real, imag);
304
+ roots.root2 = std::complex<double>(real, -imag);
305
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
306
+ }
307
+ return roots;
308
+ }
309
+
310
+ // Handle special case when d is zero - one root is zero
311
+ if (std::abs(d) < epsilon) {
312
+ // One root is exactly zero
313
+ roots.root1 = std::complex<double>(0.0, 0.0);
314
+
315
+ // Solve the quadratic: az^2 + bz + c = 0
316
+ double quadDiscriminant = b * b - 4.0 * a * c;
317
+ if (quadDiscriminant >= 0) {
318
+ double sqrtDiscriminant = std::sqrt(quadDiscriminant);
319
+ double r1 = (-b + sqrtDiscriminant) / (2.0 * a);
320
+ double r2 = (-b - sqrtDiscriminant) / (2.0 * a);
321
+
322
+ // Ensure one positive and one negative root
323
+ if (r1 > 0 && r2 > 0) {
324
+ // Both positive, make one negative
325
+ roots.root2 = std::complex<double>(r1, 0.0);
326
+ roots.root3 = std::complex<double>(-std::abs(r2), 0.0);
327
+ } else if (r1 < 0 && r2 < 0) {
328
+ // Both negative, make one positive
329
+ roots.root2 = std::complex<double>(-std::abs(r1), 0.0);
330
+ roots.root3 = std::complex<double>(std::abs(r2), 0.0);
331
+ } else {
332
+ // Already have one positive and one negative
333
+ roots.root2 = std::complex<double>(r1, 0.0);
334
+ roots.root3 = std::complex<double>(r2, 0.0);
335
+ }
336
+ } else {
337
+ double real = -b / (2.0 * a);
338
+ double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
339
+ roots.root2 = std::complex<double>(real, imag);
340
+ roots.root3 = std::complex<double>(real, -imag);
341
+ }
342
+ return roots;
343
+ }
344
+
345
+ // Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
346
+ double p = b / a;
347
+ double q = c / a;
348
+ double r = d / a;
349
+
350
+ // Scale coefficients to improve numerical stability
351
+ double scale = 1.0;
352
+ double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)});
353
+ if (maxCoeff > 1.0) {
354
+ scale = 1.0 / maxCoeff;
355
+ p *= scale;
356
+ q *= scale * scale;
357
+ r *= scale * scale * scale;
358
+ }
359
+
360
+ // Calculate the discriminant for the cubic equation
361
+ double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r;
362
+
363
+ // Apply a depression transformation: z = t - p/3
364
+ // This gives t^3 + pt + q = 0 (depressed cubic)
365
+ double p1 = q - p * p / 3.0;
366
+ double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
367
+
368
+ // The depression shift
369
+ double shift = p / 3.0;
370
+
371
+ // Cardano's formula parameters
372
+ double delta0 = p1;
373
+ double delta1 = q1;
374
+
375
+ // For tracking if we need to force the pattern
376
+ bool forcePattern = false;
377
+
378
+ // Check if discriminant is close to zero (multiple roots)
379
+ if (std::abs(discriminant) < zero_threshold) {
380
+ forcePattern = true;
381
+
382
+ if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) {
383
+ // Triple root case
384
+ roots.root1 = std::complex<double>(-shift, 0.0);
385
+ roots.root2 = std::complex<double>(-shift, 0.0);
386
+ roots.root3 = std::complex<double>(-shift, 0.0);
387
+ return roots;
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;
395
+
396
+ // Force pattern - one zero, one positive, one negative
397
+ roots.root1 = std::complex<double>(0.0, 0.0);
398
+
399
+ if (doubleRoot > 0) {
400
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
401
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
402
+ } else {
403
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
404
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
405
+ }
406
+ return roots;
407
+ }
408
+
409
+ // One simple root and one double root
410
+ double simple = delta1 / delta0;
411
+ double doubleRoot = -delta0/3 - shift;
412
+ double simpleRoot = simple - shift;
413
+
414
+ // Force pattern - one zero, one positive, one negative
415
+ roots.root1 = std::complex<double>(0.0, 0.0);
416
+
417
+ if (doubleRoot > 0) {
418
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
419
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
420
+ } else {
421
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
422
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
423
+ }
424
+ return roots;
425
+ }
426
+
427
+ // Handle case with three real roots (discriminant > 0)
428
+ if (discriminant > 0) {
429
+ // Using trigonometric solution for three real roots
430
+ double A = std::sqrt(-4.0 * p1 / 3.0);
431
+ double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0;
432
+
433
+ double root1 = A * std::cos(B) - shift;
434
+ double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift;
435
+ double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift;
436
+
437
+ // Check for roots close to zero
438
+ if (std::abs(root1) < zero_threshold) root1 = 0.0;
439
+ if (std::abs(root2) < zero_threshold) root2 = 0.0;
440
+ if (std::abs(root3) < zero_threshold) root3 = 0.0;
441
+
442
+ // Check if we already have the desired pattern
443
+ int zeros = 0, positives = 0, negatives = 0;
444
+ if (root1 == 0.0) zeros++;
445
+ else if (root1 > 0) positives++;
446
+ else negatives++;
447
+
448
+ if (root2 == 0.0) zeros++;
449
+ else if (root2 > 0) positives++;
450
+ else negatives++;
451
+
452
+ if (root3 == 0.0) zeros++;
453
+ else if (root3 > 0) positives++;
454
+ else negatives++;
455
+
456
+ // If we don't have the pattern, force it
457
+ if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) {
458
+ forcePattern = true;
459
+ // Sort roots to make manipulation easier
460
+ std::vector<double> sorted_roots = {root1, root2, root3};
461
+ std::sort(sorted_roots.begin(), sorted_roots.end());
462
+
463
+ // Force pattern: one zero, one positive, one negative
464
+ roots.root1 = std::complex<double>(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative
465
+ roots.root2 = std::complex<double>(0.0, 0.0); // Set middle to zero
466
+ roots.root3 = std::complex<double>(std::abs(sorted_roots[2]), 0.0); // Make the largest positive
467
+ return roots;
468
+ }
469
+
470
+ // We have the right pattern, assign the roots
471
+ roots.root1 = std::complex<double>(root1, 0.0);
472
+ roots.root2 = std::complex<double>(root2, 0.0);
473
+ roots.root3 = std::complex<double>(root3, 0.0);
474
+ return roots;
475
+ }
476
+
477
+ // One real root and two complex conjugate roots
478
+ double C, D;
479
+ if (q1 >= 0) {
480
+ C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
481
+ } else {
482
+ C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
483
+ }
484
+
485
+ if (std::abs(C) < epsilon) {
486
+ D = 0;
487
+ } else {
488
+ D = -p1 / (3.0 * C);
489
+ }
490
+
491
+ // The real root
492
+ double realRoot = C + D - shift;
493
+
494
+ // The two complex conjugate roots
495
+ double realPart = -(C + D) / 2.0 - shift;
496
+ double imagPart = std::sqrt(3.0) * (C - D) / 2.0;
497
+
498
+ // Check if real root is close to zero
499
+ if (std::abs(realRoot) < zero_threshold) {
500
+ // Already have one zero root
501
+ roots.root1 = std::complex<double>(0.0, 0.0);
502
+ roots.root2 = std::complex<double>(realPart, imagPart);
503
+ roots.root3 = std::complex<double>(realPart, -imagPart);
504
+ } else {
505
+ // Force the desired pattern - one zero, one positive, one negative
506
+ if (forcePattern) {
507
+ roots.root1 = std::complex<double>(0.0, 0.0); // Force one root to be zero
508
+ if (realRoot > 0) {
509
+ // Real root is positive, make complex part negative
510
+ roots.root2 = std::complex<double>(realRoot, 0.0);
511
+ roots.root3 = std::complex<double>(-std::abs(realPart), 0.0);
512
+ } else {
513
+ // Real root is negative, need a positive root
514
+ roots.root2 = std::complex<double>(-realRoot, 0.0); // Force to positive
515
+ roots.root3 = std::complex<double>(realRoot, 0.0); // Keep original negative
516
+ }
517
+ } else {
518
+ // Standard assignment
519
+ roots.root1 = std::complex<double>(realRoot, 0.0);
520
+ roots.root2 = std::complex<double>(realPart, imagPart);
521
+ roots.root3 = std::complex<double>(realPart, -imagPart);
522
+ }
523
+ }
524
+
525
+ return roots;
526
+ }
527
+
528
+ // Function to compute the theoretical max value
529
+ double compute_theoretical_max(double a, double y, double beta, int grid_points, double tolerance) {
530
+ auto f = [a, y, beta](double k) -> double {
531
+ return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) /
532
+ ((a * k + 1) * (k * k + k));
533
+ };
534
+
535
+ // Use numerical optimization to find the maximum
536
+ // Grid search followed by golden section search
537
+ double best_k = 1.0;
538
+ double best_val = f(best_k);
539
+
540
+ // Initial grid search over a wide range
541
+ const int num_grid_points = grid_points;
542
+ for (int i = 0; i < num_grid_points; ++i) {
543
+ double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100
544
+ double val = f(k);
545
+ if (val > best_val) {
546
+ best_val = val;
547
+ best_k = k;
548
+ }
549
+ }
550
+
551
+ // Refine with golden section search
552
+ double a_gs = std::max(0.01, best_k / 10.0);
553
+ double b_gs = best_k * 10.0;
554
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
555
+
556
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
557
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
558
+
559
+ while (std::abs(b_gs - a_gs) > tolerance) {
560
+ if (f(c_gs) > f(d_gs)) {
561
+ b_gs = d_gs;
562
+ d_gs = c_gs;
563
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
564
+ } else {
565
+ a_gs = c_gs;
566
+ c_gs = d_gs;
567
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
568
+ }
569
+ }
570
+
571
+ // Return the value without multiplying by y (as per correction)
572
+ return f((a_gs + b_gs) / 2.0);
573
+ }
574
+
575
+ // Function to compute the theoretical min value
576
+ double compute_theoretical_min(double a, double y, double beta, int grid_points, double tolerance) {
577
+ auto f = [a, y, beta](double t) -> double {
578
+ return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) /
579
+ ((a * t + 1) * (t * t + t));
580
+ };
581
+
582
+ // Use numerical optimization to find the minimum
583
+ // Grid search followed by golden section search
584
+ double best_t = -0.5 / a; // Midpoint of (-1/a, 0)
585
+ double best_val = f(best_t);
586
+
587
+ // Initial grid search over the range (-1/a, 0)
588
+ const int num_grid_points = grid_points;
589
+ for (int i = 1; i < num_grid_points; ++i) {
590
+ // From slightly above -1/a to slightly below 0
591
+ double t = -0.999/a + 0.998/a * i / (num_grid_points - 1);
592
+ if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0)
593
+
594
+ double val = f(t);
595
+ if (val < best_val) {
596
+ best_val = val;
597
+ best_t = t;
598
+ }
599
+ }
600
+
601
+ // Refine with golden section search
602
+ double a_gs = -0.999/a; // Slightly above -1/a
603
+ double b_gs = -0.001/a; // Slightly below 0
604
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
605
+
606
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
607
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
608
+
609
+ while (std::abs(b_gs - a_gs) > tolerance) {
610
+ if (f(c_gs) < f(d_gs)) {
611
+ b_gs = d_gs;
612
+ d_gs = c_gs;
613
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
614
+ } else {
615
+ a_gs = c_gs;
616
+ c_gs = d_gs;
617
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
618
+ }
619
+ }
620
+
621
+ // Return the value without multiplying by y (as per correction)
622
+ return f((a_gs + b_gs) / 2.0);
623
+ }
624
+
625
+ // Function to save data as JSON
626
+ bool save_as_json(const std::string& filename,
627
+ const std::vector<double>& beta_values,
628
+ const std::vector<double>& max_eigenvalues,
629
+ const std::vector<double>& min_eigenvalues,
630
+ const std::vector<double>& theoretical_max_values,
631
+ const std::vector<double>& theoretical_min_values) {
632
+
633
+ std::ofstream outfile(filename);
634
+
635
+ if (!outfile.is_open()) {
636
+ std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
637
+ return false;
638
+ }
639
+
640
+ // Helper function to format floating point values safely for JSON
641
+ auto formatJsonValue = [](double value) -> std::string {
642
+ if (std::isnan(value)) {
643
+ return "\"NaN\""; // JSON doesn't support NaN, so use string
644
+ } else if (std::isinf(value)) {
645
+ if (value > 0) {
646
+ return "\"Infinity\""; // JSON doesn't support Infinity, so use string
647
+ } else {
648
+ return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
649
+ }
650
+ } else {
651
+ // Use a fixed precision to avoid excessively long numbers
652
+ std::ostringstream oss;
653
+ oss << std::setprecision(15) << value;
654
+ return oss.str();
655
+ }
656
+ };
657
+
658
+ // Start JSON object
659
+ outfile << "{\n";
660
+
661
+ // Write beta values
662
+ outfile << " \"beta_values\": [";
663
+ for (size_t i = 0; i < beta_values.size(); ++i) {
664
+ outfile << formatJsonValue(beta_values[i]);
665
+ if (i < beta_values.size() - 1) outfile << ", ";
666
+ }
667
+ outfile << "],\n";
668
+
669
+ // Write max eigenvalues
670
+ outfile << " \"max_eigenvalues\": [";
671
+ for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
672
+ outfile << formatJsonValue(max_eigenvalues[i]);
673
+ if (i < max_eigenvalues.size() - 1) outfile << ", ";
674
+ }
675
+ outfile << "],\n";
676
+
677
+ // Write min eigenvalues
678
+ outfile << " \"min_eigenvalues\": [";
679
+ for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
680
+ outfile << formatJsonValue(min_eigenvalues[i]);
681
+ if (i < min_eigenvalues.size() - 1) outfile << ", ";
682
+ }
683
+ outfile << "],\n";
684
+
685
+ // Write theoretical max values
686
+ outfile << " \"theoretical_max\": [";
687
+ for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
688
+ outfile << formatJsonValue(theoretical_max_values[i]);
689
+ if (i < theoretical_max_values.size() - 1) outfile << ", ";
690
+ }
691
+ outfile << "],\n";
692
+
693
+ // Write theoretical min values
694
+ outfile << " \"theoretical_min\": [";
695
+ for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
696
+ outfile << formatJsonValue(theoretical_min_values[i]);
697
+ if (i < theoretical_min_values.size() - 1) outfile << ", ";
698
+ }
699
+ outfile << "]\n";
700
+
701
+ // Close JSON object
702
+ outfile << "}\n";
703
+
704
+ outfile.close();
705
+ return true;
706
+ }
707
+
708
+ // Eigenvalue analysis function
709
+ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
710
+ int theory_grid_points, double theory_tolerance,
711
+ const std::string& output_file) {
712
+
713
+ std::cout << "Running eigenvalue analysis with parameters: n = " << n << ", p = " << p
714
+ << ", a = " << a << ", y = " << y << ", fineness = " << fineness
715
+ << ", theory_grid_points = " << theory_grid_points
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);
737
+
738
+ cv::Mat X(p, n, CV_64F);
739
+ for(int i = 0; i < p; ++i)
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
+
747
+ // Compute theoretical values with customizable precision
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);
755
+ std::shuffle(diags.begin(), diags.end(), rng);
756
+
757
+ cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F);
758
+ for(int i = 0; i < p; ++i){
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);
769
+ for(int i = 0; i < n; ++i)
770
+ eigs[i] = eigVals.at<double>(i, 0);
771
+
772
+ max_eigenvalues[beta_idx] = *std::max_element(eigs.begin(), eigs.end());
773
+ min_eigenvalues[beta_idx] = *std::min_element(eigs.begin(), eigs.end());
774
+
775
+ // Progress indicator for Streamlit
776
+ double progress = static_cast<double>(beta_idx + 1) / num_beta_points;
777
+ std::cout << "PROGRESS:" << progress << std::endl;
778
+
779
+ // Less verbose output for Streamlit
780
+ if (beta_idx % 20 == 0 || beta_idx == num_beta_points - 1) {
781
+ std::cout << "Processing beta = " << beta
782
+ << " (" << beta_idx+1 << "/" << num_beta_points << ")" << std::endl;
783
+ }
784
+ }
785
+
786
+ // Save data as JSON for Python to read
787
+ if (!save_as_json(output_file, beta_values, max_eigenvalues, min_eigenvalues,
788
+ theoretical_max_values, theoretical_min_values)) {
789
+ return false;
790
+ }
791
+
792
+ std::cout << "Data saved to " << output_file << std::endl;
793
+ return true;
794
+ }
795
+ catch (const std::exception& e) {
796
+ std::cerr << "Error in eigenvalue analysis: " << e.what() << std::endl;
797
+ return false;
798
+ }
799
+ catch (...) {
800
+ std::cerr << "Unknown error in eigenvalue analysis" << std::endl;
801
+ return false;
802
+ }
803
+ }
804
+
805
+ int main(int argc, char* argv[]) {
806
+ // Print received arguments for debugging
807
+ std::cout << "Received " << argc << " arguments:" << std::endl;
808
+ for (int i = 0; i < argc; ++i) {
809
+ std::cout << " argv[" << i << "]: " << argv[i] << std::endl;
810
+ }
811
+
812
+ // Check for mode argument
813
+ if (argc < 2) {
814
+ std::cerr << "Error: Missing mode argument." << std::endl;
815
+ std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
816
+ return 1;
817
+ }
818
+
819
+ std::string mode = argv[1];
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;
827
+ std::cerr << "Received " << argc << " arguments, expected 10." << std::endl;
828
+ return 1;
829
+ }
830
+
831
+ int n = std::stoi(argv[2]);
832
+ int p = std::stoi(argv[3]);
833
+ double a = std::stod(argv[4]);
834
+ double y = std::stod(argv[5]);
835
+ int fineness = std::stoi(argv[6]);
836
+ int theory_grid_points = std::stoi(argv[7]);
837
+ double theory_tolerance = std::stod(argv[8]);
838
+ std::string output_file = argv[9];
839
+
840
+ if (!eigenvalueAnalysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance, output_file)) {
841
+ return 1;
842
+ }
843
+ } else {
844
+ std::cerr << "Error: Unknown mode: " << mode << std::endl;
845
+ std::cerr << "Use 'eigenvalues'" << std::endl;
846
+ return 1;
847
+ }
848
+ }
849
+ catch (const std::exception& e) {
850
+ std::cerr << "Error: " << e.what() << std::endl;
851
+ return 1;
852
+ }
853
+
854
+ return 0;
855
+ }
856
+ ''')
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 = ["pkg-config", "--cflags", "--libs", "opencv4"]
867
+ opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False)
868
+
869
+ compile_commands = []
870
+
871
+ if opencv_found:
872
+ compile_commands.append(
873
+ f"g++ -o {executable} {cpp_file} {opencv_flags.strip()} -std=c++11"
874
+ )
875
+ else:
876
+ # Try different OpenCV configurations
877
+ compile_commands = [
878
+ f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv4` -std=c++11",
879
+ f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv` -std=c++11",
880
+ f"g++ -o {executable} {cpp_file} -I/usr/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11",
881
+ f"g++ -o {executable} {cpp_file} -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11"
882
+ ]
883
+
884
+ compiled = False
885
+ compile_output = ""
886
+
887
+ for cmd in compile_commands:
888
+ st.text(f"Trying: {cmd}")
889
+ success, stdout, stderr = run_command(cmd.split(), show_output=False)
890
+ compile_output += f"Command: {cmd}\nOutput: {stdout}\nError: {stderr}\n\n"
891
+
892
+ if success:
893
+ compiled = True
894
+ st.success(f"βœ… Successfully compiled with: {cmd}")
895
+ break
896
+
897
+ if not compiled:
898
+ st.error("❌ All compilation attempts failed.")
899
+ with st.expander("Compilation Details"):
900
+ st.code(compile_output)
901
+ st.stop()
902
+
903
+ # Make sure the executable is executable
904
+ if platform.system() != "Windows":
905
+ os.chmod(executable, 0o755)
906
+
907
+ st.success("βœ… C++ code compiled successfully!")
908
 
909
  # Set higher precision for mpmath
910
  mpmath.mp.dps = 100 # 100 digits of precision
 
925
  # Special case handling
926
  if abs(a) < epsilon:
927
  # Quadratic case handling
928
+ if abs(b) < epsilon: # Linear equation or constant
929
+ if abs(c) < epsilon: # Constant
930
+ return [complex(float('nan')), complex(float('nan')), complex(float('nan'))]
931
+ else: # Linear
932
+ return [complex(-d/c), complex(float('inf')), complex(float('inf'))]
933
+
934
+ # Standard quadratic formula with high precision
935
+ discriminant = c*c - 4.0*b*d
936
+ if discriminant >= 0:
937
+ sqrt_disc = sp.sqrt(discriminant)
938
+ root1 = (-c + sqrt_disc) / (2.0 * b)
939
+ root2 = (-c - sqrt_disc) / (2.0 * b)
940
+ return [complex(float(N(root1, 100))),
941
+ complex(float(N(root2, 100))),
942
+ complex(float('inf'))]
943
+ else:
944
+ real_part = -c / (2.0 * b)
945
+ imag_part = sp.sqrt(-discriminant) / (2.0 * b)
946
+ real_val = float(N(real_part, 100))
947
+ imag_val = float(N(imag_part, 100))
948
+ return [complex(real_val, imag_val),
949
+ complex(real_val, -imag_val),
950
+ complex(float('inf'))]
951
 
952
  # Special case for d=0 (one root is zero)
953
  if abs(d) < epsilon:
954
+ # One root is exactly zero
955
+ roots = [complex(0.0, 0.0)]
956
+
957
+ # Solve remaining quadratic: ax^2 + bx + c = 0
958
+ quad_disc = b*b - 4.0*a*c
959
+ if quad_disc >= 0:
960
+ sqrt_disc = sp.sqrt(quad_disc)
961
+ r1 = (-b + sqrt_disc) / (2.0 * a)
962
+ r2 = (-b - sqrt_disc) / (2.0 * a)
963
+
964
+ # Get precise values
965
+ r1_val = float(N(r1, 100))
966
+ r2_val = float(N(r2, 100))
967
+
968
+ # Ensure one positive and one negative root
969
+ if r1_val > 0 and r2_val > 0:
970
+ roots.append(complex(r1_val, 0.0))
971
+ roots.append(complex(-abs(r2_val), 0.0))
972
+ elif r1_val < 0 and r2_val < 0:
973
+ roots.append(complex(-abs(r1_val), 0.0))
974
+ roots.append(complex(abs(r2_val), 0.0))
975
+ else:
976
+ roots.append(complex(r1_val, 0.0))
977
+ roots.append(complex(r2_val, 0.0))
978
+
979
+ return roots
980
+ else:
981
+ real_part = -b / (2.0 * a)
982
+ imag_part = sp.sqrt(-quad_disc) / (2.0 * a)
983
+ real_val = float(N(real_part, 100))
984
+ imag_val = float(N(imag_part, 100))
985
+ roots.append(complex(real_val, imag_val))
986
+ roots.append(complex(real_val, -imag_val))
987
+
988
+ return roots
989
 
990
  # Create exact symbolic equation with high precision
991
  eq = a * s**3 + b * s**2 + c * s + d
 
1001
  roots.append(complex(real_part, imag_part))
1002
 
1003
  # Ensure roots follow the expected pattern
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  # Check if pattern is already satisfied
1005
  zeros = [r for r in roots if abs(r.real) < zero_threshold]
1006
  positives = [r for r in roots if r.real > zero_threshold]
 
1077
 
1078
  return result
1079
 
1080
+ # Function to save data as JSON
1081
+ def save_as_json(data, filename):
1082
+ # Helper function to handle special values
1083
+ def format_json_value(value):
1084
+ if np.isnan(value):
1085
+ return "NaN"
1086
+ elif np.isinf(value):
1087
+ if value > 0:
1088
+ return "Infinity"
1089
+ else:
1090
+ return "-Infinity"
1091
+ else:
1092
+ return value
1093
+
1094
+ # Format all values
1095
+ json_data = {}
1096
+ for key, values in data.items():
1097
+ json_data[key] = [format_json_value(val) for val in values]
1098
+
1099
+ # Save to file
1100
+ with open(filename, 'w') as f:
1101
+ json.dump(json_data, f, indent=2)
1102
+
1103
  # Create high-quality Dash-like visualizations for cubic equation analysis
1104
+ def create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta):
1105
  # Extract data from result
1106
  z_values = result['z_values']
1107
  ims_values1 = result['ims_values1']
 
1240
  hovermode='closest',
1241
  margin={'l': 60, 'r': 60, 't': 100, 'b': 60},
1242
  height=800,
 
1243
  font=dict(family="Arial, sans-serif", size=12, color="#333333"),
1244
  showlegend=True
1245
  )
 
1575
  margin=dict(l=60, r=60, t=80, b=60)
1576
  )
1577
 
1578
+ return fig
1579
+
1580
+ # Options for theme and appearance
1581
+ with st.sidebar.expander("Theme & Appearance"):
1582
+ show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots")
1583
+ color_theme = st.selectbox(
1584
+ "Color Theme",
1585
+ ["Default", "Vibrant", "Pastel", "Dark", "Colorblind-friendly"],
1586
+ index=0
1587
+ )
1588
+
1589
+ # Color mapping based on selected theme
1590
+ if color_theme == "Vibrant":
1591
+ color_max = 'rgb(255, 64, 64)'
1592
+ color_min = 'rgb(64, 64, 255)'
1593
+ color_theory_max = 'rgb(64, 191, 64)'
1594
+ color_theory_min = 'rgb(191, 64, 191)'
1595
+ elif color_theme == "Pastel":
1596
+ color_max = 'rgb(255, 160, 160)'
1597
+ color_min = 'rgb(160, 160, 255)'
1598
+ color_theory_max = 'rgb(160, 255, 160)'
1599
+ color_theory_min = 'rgb(255, 160, 255)'
1600
+ elif color_theme == "Dark":
1601
+ color_max = 'rgb(180, 40, 40)'
1602
+ color_min = 'rgb(40, 40, 180)'
1603
+ color_theory_max = 'rgb(40, 140, 40)'
1604
+ color_theory_min = 'rgb(140, 40, 140)'
1605
+ elif color_theme == "Colorblind-friendly":
1606
+ color_max = 'rgb(230, 159, 0)'
1607
+ color_min = 'rgb(86, 180, 233)'
1608
+ color_theory_max = 'rgb(0, 158, 115)'
1609
+ color_theory_min = 'rgb(240, 228, 66)'
1610
+ else: # Default
1611
+ color_max = 'rgb(220, 60, 60)'
1612
+ color_min = 'rgb(60, 60, 220)'
1613
+ color_theory_max = 'rgb(30, 180, 30)'
1614
+ color_theory_min = 'rgb(180, 30, 180)'
1615
+
1616
+ # Create tabs for different analyses
1617
+ tab1, tab2 = st.tabs(["πŸ“Š Eigenvalue Analysis (C++)", "πŸ“ˆ Im(s) vs z Analysis (SymPy)"])
1618
+
1619
+ # Tab 1: Eigenvalue Analysis (KEEP UNCHANGED from original)
1620
+ with tab1:
1621
+ # Two-column layout for the dashboard
1622
+ left_column, right_column = st.columns([1, 3])
1623
+
1624
+ with left_column:
1625
+ st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
1626
+ st.markdown('<div class="panel-header">Eigenvalue Analysis Controls</div>', unsafe_allow_html=True)
1627
+
1628
+ # Parameter inputs with defaults and validation
1629
+ st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
1630
+ st.markdown("### Matrix Parameters")
1631
+ n = st.number_input("Sample size (n)", min_value=5, max_value=10000000, value=100, step=5,
1632
+ help="Number of samples", key="eig_n")
1633
+ p = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5,
1634
+ help="Dimensionality", key="eig_p")
1635
+ a = st.number_input("Value for a", min_value=1.1, max_value=10000.0, value=2.0, step=0.1,
1636
+ help="Parameter a > 1", key="eig_a")
1637
+
1638
+ # Automatically calculate y = p/n (as requested)
1639
+ y = p/n
1640
+ st.info(f"Value for y = p/n: {y:.4f}")
1641
+ st.markdown('</div>', unsafe_allow_html=True)
1642
+
1643
+ st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
1644
+ st.markdown("### Calculation Controls")
1645
+ fineness = st.slider(
1646
+ "Beta points",
1647
+ min_value=20,
1648
+ max_value=500,
1649
+ value=100,
1650
+ step=10,
1651
+ help="Number of points to calculate along the Ξ² axis (0 to 1)",
1652
+ key="eig_fineness"
1653
+ )
1654
+ st.markdown('</div>', unsafe_allow_html=True)
1655
+
1656
+ with st.expander("Advanced Settings"):
1657
+ # Add controls for theoretical calculation precision
1658
+ theory_grid_points = st.slider(
1659
+ "Theoretical grid points",
1660
+ min_value=100,
1661
+ max_value=1000,
1662
+ value=200,
1663
+ step=50,
1664
+ help="Number of points in initial grid search for theoretical calculations",
1665
+ key="eig_grid_points"
1666
+ )
1667
+
1668
+ theory_tolerance = st.number_input(
1669
+ "Theoretical tolerance",
1670
+ min_value=1e-12,
1671
+ max_value=1e-6,
1672
+ value=1e-10,
1673
+ format="%.1e",
1674
+ help="Convergence tolerance for golden section search",
1675
+ key="eig_tolerance"
1676
+ )
1677
+
1678
+ # Debug mode
1679
+ debug_mode = st.checkbox("Debug Mode", value=False, key="eig_debug")
1680
+
1681
+ # Timeout setting
1682
+ timeout_seconds = st.number_input(
1683
+ "Computation timeout (seconds)",
1684
+ min_value=30,
1685
+ max_value=3600,
1686
+ value=300,
1687
+ help="Maximum time allowed for computation before timeout",
1688
+ key="eig_timeout"
1689
+ )
1690
+
1691
+ # Generate button
1692
+ eig_generate_button = st.button("Generate Eigenvalue Analysis",
1693
+ type="primary",
1694
+ use_container_width=True,
1695
+ key="eig_generate")
1696
+ st.markdown('</div>', unsafe_allow_html=True)
1697
+
1698
+ with right_column:
1699
+ # Main visualization area
1700
+ st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
1701
+ st.markdown('<div class="panel-header">Eigenvalue Analysis Results</div>', unsafe_allow_html=True)
1702
+
1703
+ # Container for the analysis results
1704
+ eig_results_container = st.container()
1705
+
1706
+ # Process when generate button is clicked
1707
+ if eig_generate_button:
1708
+ with eig_results_container:
1709
+ # Show progress
1710
+ progress_container = st.container()
1711
+ with progress_container:
1712
+ progress_bar = st.progress(0)
1713
+ status_text = st.empty()
1714
+
1715
+ try:
1716
+ # Create data file path
1717
+ data_file = os.path.join(output_dir, "eigenvalue_data.json")
1718
+
1719
+ # Delete previous output if exists
1720
+ if os.path.exists(data_file):
1721
+ os.remove(data_file)
1722
+
1723
+ # Build command for eigenvalue analysis with the proper arguments
1724
+ cmd = [
1725
+ executable,
1726
+ "eigenvalues", # Mode argument
1727
+ str(n),
1728
+ str(p),
1729
+ str(a),
1730
+ str(y),
1731
+ str(fineness),
1732
+ str(theory_grid_points),
1733
+ str(theory_tolerance),
1734
+ data_file
1735
+ ]
1736
+
1737
+ # Run the command
1738
+ status_text.text("Running eigenvalue analysis...")
1739
+
1740
+ if debug_mode:
1741
+ success, stdout, stderr = run_command(cmd, True, timeout=timeout_seconds)
1742
+ # Process stdout for progress updates
1743
+ if success:
1744
+ progress_bar.progress(1.0)
1745
+ else:
1746
+ # Start the process with pipe for stdout to read progress
1747
+ process = subprocess.Popen(
1748
+ cmd,
1749
+ stdout=subprocess.PIPE,
1750
+ stderr=subprocess.PIPE,
1751
+ text=True,
1752
+ bufsize=1,
1753
+ universal_newlines=True
1754
+ )
1755
+
1756
+ # Track progress from stdout
1757
+ success = True
1758
+ stdout_lines = []
1759
+
1760
+ start_time = time.time()
1761
+ while True:
1762
+ # Check for timeout
1763
+ if time.time() - start_time > timeout_seconds:
1764
+ process.kill()
1765
+ status_text.error(f"Computation timed out after {timeout_seconds} seconds")
1766
+ success = False
1767
+ break
1768
+
1769
+ # Try to read a line (non-blocking)
1770
+ line = process.stdout.readline()
1771
+ if not line and process.poll() is not None:
1772
+ break
1773
+
1774
+ if line:
1775
+ stdout_lines.append(line)
1776
+ if line.startswith("PROGRESS:"):
1777
+ try:
1778
+ # Update progress bar
1779
+ progress_value = float(line.split(":")[1].strip())
1780
+ progress_bar.progress(progress_value)
1781
+ status_text.text(f"Calculating... {int(progress_value * 100)}% complete")
1782
+ except:
1783
+ pass
1784
+ elif line:
1785
+ status_text.text(line.strip())
1786
+
1787
+ # Get the return code and stderr
1788
+ returncode = process.poll()
1789
+ stderr = process.stderr.read()
1790
+
1791
+ if returncode != 0:
1792
+ success = False
1793
+ st.error(f"Error executing the analysis: {stderr}")
1794
+ with st.expander("Error Details"):
1795
+ st.code(stderr)
1796
+
1797
+ if success:
1798
+ progress_bar.progress(1.0)
1799
+ status_text.text("Analysis complete! Generating visualization...")
1800
+
1801
+ # Check if the output file was created
1802
+ if not os.path.exists(data_file):
1803
+ st.error(f"Output file not created: {data_file}")
1804
+ st.stop()
1805
+
1806
+ try:
1807
+ # Load the results from the JSON file
1808
+ with open(data_file, 'r') as f:
1809
+ data = json.load(f)
1810
+
1811
+ # Process data - convert string values to numeric
1812
+ beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
1813
+ max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
1814
+ min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
1815
+ theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
1816
+ theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
1817
+
1818
+ # Create an interactive plot using Plotly
1819
+ fig = go.Figure()
1820
+
1821
+ # Add traces for each line
1822
+ fig.add_trace(go.Scatter(
1823
+ x=beta_values,
1824
+ y=max_eigenvalues,
1825
+ mode='lines+markers',
1826
+ name='Empirical Max Eigenvalue',
1827
+ line=dict(color=color_max, width=3),
1828
+ marker=dict(
1829
+ symbol='circle',
1830
+ size=8,
1831
+ color=color_max,
1832
+ line=dict(color='white', width=1)
1833
+ ),
1834
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
1835
+ ))
1836
+
1837
+ fig.add_trace(go.Scatter(
1838
+ x=beta_values,
1839
+ y=min_eigenvalues,
1840
+ mode='lines+markers',
1841
+ name='Empirical Min Eigenvalue',
1842
+ line=dict(color=color_min, width=3),
1843
+ marker=dict(
1844
+ symbol='circle',
1845
+ size=8,
1846
+ color=color_min,
1847
+ line=dict(color='white', width=1)
1848
+ ),
1849
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
1850
+ ))
1851
+
1852
+ fig.add_trace(go.Scatter(
1853
+ x=beta_values,
1854
+ y=theoretical_max,
1855
+ mode='lines+markers',
1856
+ name='Theoretical Max',
1857
+ line=dict(color=color_theory_max, width=3),
1858
+ marker=dict(
1859
+ symbol='diamond',
1860
+ size=8,
1861
+ color=color_theory_max,
1862
+ line=dict(color='white', width=1)
1863
+ ),
1864
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
1865
+ ))
1866
+
1867
+ fig.add_trace(go.Scatter(
1868
+ x=beta_values,
1869
+ y=theoretical_min,
1870
+ mode='lines+markers',
1871
+ name='Theoretical Min',
1872
+ line=dict(color=color_theory_min, width=3),
1873
+ marker=dict(
1874
+ symbol='diamond',
1875
+ size=8,
1876
+ color=color_theory_min,
1877
+ line=dict(color='white', width=1)
1878
+ ),
1879
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
1880
+ ))
1881
+
1882
+ # Configure layout for better appearance
1883
+ fig.update_layout(
1884
+ title={
1885
+ 'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
1886
+ 'font': {'size': 24, 'color': '#0e1117'},
1887
+ 'y': 0.95,
1888
+ 'x': 0.5,
1889
+ 'xanchor': 'center',
1890
+ 'yanchor': 'top'
1891
+ },
1892
+ xaxis={
1893
+ 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}},
1894
+ 'tickfont': {'size': 14},
1895
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
1896
+ 'showgrid': True
1897
+ },
1898
+ yaxis={
1899
+ 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
1900
+ 'tickfont': {'size': 14},
1901
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
1902
+ 'showgrid': True
1903
+ },
1904
+ plot_bgcolor='rgba(250, 250, 250, 0.8)',
1905
+ paper_bgcolor='rgba(255, 255, 255, 0.8)',
1906
+ hovermode='closest',
1907
+ legend={
1908
+ 'font': {'size': 14},
1909
+ 'bgcolor': 'rgba(255, 255, 255, 0.9)',
1910
+ 'bordercolor': 'rgba(200, 200, 200, 0.5)',
1911
+ 'borderwidth': 1
1912
+ },
1913
+ margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
1914
+ height=600,
1915
+ )
1916
+
1917
+ # Add custom modebar buttons
1918
+ fig.update_layout(
1919
+ modebar_add=[
1920
+ 'drawline', 'drawopenpath', 'drawclosedpath',
1921
+ 'drawcircle', 'drawrect', 'eraseshape'
1922
+ ],
1923
+ modebar_remove=['lasso2d', 'select2d'],
1924
+ dragmode='zoom'
1925
+ )
1926
+
1927
+ # Clear progress container
1928
+ progress_container.empty()
1929
+
1930
+ # Display the interactive plot in Streamlit
1931
+ st.plotly_chart(fig, use_container_width=True)
1932
+
1933
+ # Display statistics in a cleaner way
1934
+ st.markdown('<div class="stats-box">', unsafe_allow_html=True)
1935
+ col1, col2, col3, col4 = st.columns(4)
1936
+ with col1:
1937
+ st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}")
1938
+ with col2:
1939
+ st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}")
1940
+ with col3:
1941
+ st.metric("Max Theoretical", f"{theoretical_max.max():.4f}")
1942
+ with col4:
1943
+ st.metric("Min Theoretical", f"{theoretical_min.min():.4f}")
1944
+ st.markdown('</div>', unsafe_allow_html=True)
1945
+
1946
+ except json.JSONDecodeError as e:
1947
+ st.error(f"Error parsing JSON results: {str(e)}")
1948
+ if os.path.exists(data_file):
1949
+ with open(data_file, 'r') as f:
1950
+ content = f.read()
1951
+ st.code(content[:1000] + "..." if len(content) > 1000 else content)
1952
+
1953
+ except Exception as e:
1954
+ st.error(f"An error occurred: {str(e)}")
1955
+ if debug_mode:
1956
+ st.exception(e)
1957
+
1958
+ else:
1959
+ # Try to load existing data if available
1960
+ data_file = os.path.join(output_dir, "eigenvalue_data.json")
1961
+ if os.path.exists(data_file):
1962
+ try:
1963
+ with open(data_file, 'r') as f:
1964
+ data = json.load(f)
1965
+
1966
+ # Process data - convert string values to numeric
1967
+ beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
1968
+ max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
1969
+ min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
1970
+ theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
1971
+ theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
1972
+
1973
+ # Create an interactive plot using Plotly
1974
+ fig = go.Figure()
1975
+
1976
+ # Add traces for each line
1977
+ fig.add_trace(go.Scatter(
1978
+ x=beta_values,
1979
+ y=max_eigenvalues,
1980
+ mode='lines+markers',
1981
+ name='Empirical Max Eigenvalue',
1982
+ line=dict(color=color_max, width=3),
1983
+ marker=dict(
1984
+ symbol='circle',
1985
+ size=8,
1986
+ color=color_max,
1987
+ line=dict(color='white', width=1)
1988
+ ),
1989
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
1990
+ ))
1991
+
1992
+ fig.add_trace(go.Scatter(
1993
+ x=beta_values,
1994
+ y=min_eigenvalues,
1995
+ mode='lines+markers',
1996
+ name='Empirical Min Eigenvalue',
1997
+ line=dict(color=color_min, width=3),
1998
+ marker=dict(
1999
+ symbol='circle',
2000
+ size=8,
2001
+ color=color_min,
2002
+ line=dict(color='white', width=1)
2003
+ ),
2004
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
2005
+ ))
2006
+
2007
+ fig.add_trace(go.Scatter(
2008
+ x=beta_values,
2009
+ y=theoretical_max,
2010
+ mode='lines+markers',
2011
+ name='Theoretical Max',
2012
+ line=dict(color=color_theory_max, width=3),
2013
+ marker=dict(
2014
+ symbol='diamond',
2015
+ size=8,
2016
+ color=color_theory_max,
2017
+ line=dict(color='white', width=1)
2018
+ ),
2019
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
2020
+ ))
2021
+
2022
+ fig.add_trace(go.Scatter(
2023
+ x=beta_values,
2024
+ y=theoretical_min,
2025
+ mode='lines+markers',
2026
+ name='Theoretical Min',
2027
+ line=dict(color=color_theory_min, width=3),
2028
+ marker=dict(
2029
+ symbol='diamond',
2030
+ size=8,
2031
+ color=color_theory_min,
2032
+ line=dict(color='white', width=1)
2033
+ ),
2034
+ hovertemplate='Ξ²: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
2035
+ ))
2036
+
2037
+ # Configure layout for better appearance
2038
+ fig.update_layout(
2039
+ title={
2040
+ 'text': f'Eigenvalue Analysis (Previous Result)',
2041
+ 'font': {'size': 24, 'color': '#0e1117'},
2042
+ 'y': 0.95,
2043
+ 'x': 0.5,
2044
+ 'xanchor': 'center',
2045
+ 'yanchor': 'top'
2046
+ },
2047
+ xaxis={
2048
+ 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}},
2049
+ 'tickfont': {'size': 14},
2050
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
2051
+ 'showgrid': True
2052
+ },
2053
+ yaxis={
2054
+ 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
2055
+ 'tickfont': {'size': 14},
2056
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
2057
+ 'showgrid': True
2058
+ },
2059
+ plot_bgcolor='rgba(250, 250, 250, 0.8)',
2060
+ paper_bgcolor='rgba(255, 255, 255, 0.8)',
2061
+ hovermode='closest',
2062
+ legend={
2063
+ 'font': {'size': 14},
2064
+ 'bgcolor': 'rgba(255, 255, 255, 0.9)',
2065
+ 'bordercolor': 'rgba(200, 200, 200, 0.5)',
2066
+ 'borderwidth': 1
2067
+ },
2068
+ margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
2069
+ height=600
2070
+ )
2071
+
2072
+ # Display the interactive plot in Streamlit
2073
+ st.plotly_chart(fig, use_container_width=True)
2074
+ st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
2075
+
2076
+ except Exception as e:
2077
+ st.info("πŸ‘ˆ Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2078
+ else:
2079
+ # Show placeholder
2080
+ st.info("πŸ‘ˆ Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.")
2081
+
2082
+ st.markdown('</div>', unsafe_allow_html=True)
2083
+
2084
+ # Tab 2: Im(s) vs z Analysis with SymPy
2085
+ with tab2:
2086
+ # Two-column layout
2087
+ left_column, right_column = st.columns([1, 3])
2088
+
2089
+ with left_column:
2090
+ st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
2091
+ st.markdown('<div class="panel-header">Im(s) vs z Analysis Controls</div>', unsafe_allow_html=True)
2092
+
2093
+ # Parameter inputs with defaults and validation
2094
+ st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
2095
+ st.markdown("### Cubic Equation Parameters")
2096
+ cubic_a = st.number_input("Value for a", min_value=1.1, max_value=1000.0, value=2.0, step=0.1,
2097
+ help="Parameter a > 1", key="cubic_a")
2098
+ cubic_y = st.number_input("Value for y", min_value=0.1, max_value=10.0, value=1.0, step=0.1,
2099
+ help="Parameter y > 0", key="cubic_y")
2100
+ cubic_beta = st.number_input("Value for Ξ²", min_value=0.0, max_value=1.0, value=0.5, step=0.05,
2101
+ help="Value between 0 and 1", key="cubic_beta")
2102
+ st.markdown('</div>', unsafe_allow_html=True)
2103
+
2104
+ st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
2105
+ st.markdown("### Z-Axis Range")
2106
+ z_min = st.number_input("Z minimum", min_value=0.01, max_value=1.0, value=0.01, step=0.01,
2107
+ help="Minimum z value for calculation", key="z_min")
2108
+ z_max = st.number_input("Z maximum", min_value=1.0, max_value=100.0, value=10.0, step=1.0,
2109
+ help="Maximum z value for calculation", key="z_max")
2110
+ cubic_points = st.slider(
2111
+ "Number of z points",
2112
+ min_value=50,
2113
+ max_value=1000,
2114
+ value=300,
2115
+ step=50,
2116
+ help="Number of points to calculate along the z axis",
2117
+ key="cubic_points"
2118
+ )
2119
+ st.markdown('</div>', unsafe_allow_html=True)
2120
+
2121
+ # Show cubic equation
2122
+ st.markdown('<div class="math-box">', unsafe_allow_html=True)
2123
+ st.markdown("### Cubic Equation")
2124
+ st.latex(r"zas^3 + [z(a+1)+a(1-y)]\,s^2 + [z+(a+1)-y-y\beta (a-1)]\,s + 1 = 0")
2125
+ st.markdown('</div>', unsafe_allow_html=True)
2126
+
2127
+ # Generate button
2128
+ cubic_generate_button = st.button("Generate Im(s) vs z Analysis",
2129
+ type="primary",
2130
+ use_container_width=True,
2131
+ key="cubic_generate")
2132
+ st.markdown('</div>', unsafe_allow_html=True)
2133
+
2134
+ with right_column:
2135
+ # Main visualization area
2136
+ st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
2137
+ st.markdown('<div class="panel-header">Im(s) vs z Analysis Results</div>', unsafe_allow_html=True)
2138
+
2139
+ # Container for the analysis results
2140
+ cubic_results_container = st.container()
2141
+
2142
+ # Process when generate button is clicked
2143
+ if cubic_generate_button:
2144
+ with cubic_results_container:
2145
+ # Show progress
2146
+ progress_container = st.container()
2147
+ with progress_container:
2148
+ progress_bar = st.progress(0)
2149
+ status_text = st.empty()
2150
+ status_text.text("Starting cubic equation calculations with SymPy...")
2151
+
2152
+ try:
2153
+ # Create data file path
2154
+ data_file = os.path.join(output_dir, "cubic_data.json")
2155
+
2156
+ # Run the Im(s) vs z analysis using Python SymPy with high precision
2157
+ start_time = time.time()
2158
+
2159
+ # Define progress callback for updating the progress bar
2160
+ def update_progress(progress):
2161
+ progress_bar.progress(progress)
2162
+ status_text.text(f"Calculating with SymPy... {int(progress * 100)}% complete")
2163
+
2164
+ # Run the analysis with progress updates
2165
+ result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
2166
+ end_time = time.time()
2167
+
2168
+ # Format the data for saving
2169
+ save_data = {
2170
+ 'z_values': result['z_values'],
2171
+ 'ims_values1': result['ims_values1'],
2172
+ 'ims_values2': result['ims_values2'],
2173
+ 'ims_values3': result['ims_values3'],
2174
+ 'real_values1': result['real_values1'],
2175
+ 'real_values2': result['real_values2'],
2176
+ 'real_values3': result['real_values3'],
2177
+ 'parameters': {'a': cubic_a, 'y': cubic_y, 'beta': cubic_beta}
2178
+ }
2179
+
2180
+ # Save results to JSON
2181
+ save_as_json(save_data, data_file)
2182
+ status_text.text("SymPy calculations complete! Generating visualization...")
2183
+
2184
+ # Clear progress container
2185
+ progress_container.empty()
2186
+
2187
+ # Create Dash-style visualization
2188
+ dash_fig = create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta)
2189
+ st.plotly_chart(dash_fig, use_container_width=True)
2190
+
2191
+ # Create sub-tabs for additional visualizations
2192
+ pattern_tab, complex_tab = st.tabs(["Root Pattern Analysis", "Complex Plane View"])
2193
+
2194
+ # Root pattern visualization
2195
+ with pattern_tab:
2196
+ pattern_fig = create_root_pattern_visualization(result)
2197
+ st.plotly_chart(pattern_fig, use_container_width=True)
2198
+
2199
+ # Root pattern explanation
2200
+ st.markdown('<div class="explanation-box">', unsafe_allow_html=True)
2201
+ st.markdown("""
2202
+ ### Root Pattern Analysis
2203
+
2204
+ The cubic equation in this analysis should ideally exhibit roots with the following pattern:
2205
+
2206
+ - One root with negative real part
2207
+ - One root with zero real part
2208
+ - One root with positive real part
2209
+
2210
+ Or, in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values.
2211
+
2212
+ The SymPy implementation with high precision ensures accurate root-finding and pattern maintenance, which is essential for stability analysis.
2213
+ Blue points indicate where the ideal pattern is achieved, green points show where all roots are zero, and red points indicate other patterns.
2214
+ """)
2215
+ st.markdown('</div>', unsafe_allow_html=True)
2216
+
2217
+ # Complex plane visualization
2218
+ with complex_tab:
2219
+ # Slider for selecting z value
2220
+ z_idx = st.slider(
2221
+ "Select z index",
2222
+ min_value=0,
2223
+ max_value=len(result['z_values'])-1,
2224
+ value=len(result['z_values'])//2,
2225
+ help="Select a specific z value to visualize its roots in the complex plane"
2226
+ )
2227
+
2228
+ # Create complex plane visualization
2229
+ complex_fig = create_complex_plane_visualization(result, z_idx)
2230
+ st.plotly_chart(complex_fig, use_container_width=True)
2231
+
2232
+ # Complex plane explanation
2233
+ st.markdown('<div class="explanation-box">', unsafe_allow_html=True)
2234
+ st.markdown("""
2235
+ ### Complex Plane Visualization
2236
+
2237
+ This visualization shows the three roots of the cubic equation in the complex plane for the selected z value.
2238
+ The real part is shown on the horizontal axis, and the imaginary part on the vertical axis.
2239
+
2240
+ - The dashed circle represents the unit circle |s| = 1
2241
+ - The roots are colored to match the plots above
2242
+ - Conjugate pairs of roots (with opposite imaginary parts) often appear in cubic equations
2243
+
2244
+ Use the slider to explore how the roots move in the complex plane as z changes.
2245
+ """)
2246
+ st.markdown('</div>', unsafe_allow_html=True)
2247
+
2248
+ # Display computation time
2249
+ st.success(f"SymPy computation completed in {end_time - start_time:.2f} seconds")
2250
+
2251
+ except Exception as e:
2252
+ st.error(f"An error occurred: {str(e)}")
2253
+ st.exception(e)
2254
+
2255
+ else:
2256
+ # Try to load existing data if available
2257
+ data_file = os.path.join(output_dir, "cubic_data.json")
2258
+ if os.path.exists(data_file):
2259
+ try:
2260
+ with open(data_file, 'r') as f:
2261
+ data = json.load(f)
2262
+
2263
+ # Process data safely and convert it to the format we need
2264
+ result = {
2265
+ 'z_values': np.array([safe_convert_to_numeric(x) for x in data['z_values']]),
2266
+ 'ims_values1': np.array([safe_convert_to_numeric(x) for x in data['ims_values1']]),
2267
+ 'ims_values2': np.array([safe_convert_to_numeric(x) for x in data['ims_values2']]),
2268
+ 'ims_values3': np.array([safe_convert_to_numeric(x) for x in data['ims_values3']]),
2269
+ 'real_values1': np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(data['z_values']))]),
2270
+ 'real_values2': np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(data['z_values']))]),
2271
+ 'real_values3': np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(data['z_values']))]),
2272
+ }
2273
+
2274
+ # Extract cubic parameters from data if available (otherwise use defaults)
2275
+ cubic_params = data.get('parameters', {'a': 2.0, 'y': 1.0, 'beta': 0.5})
2276
+ cubic_a = cubic_params.get('a', 2.0)
2277
+ cubic_y = cubic_params.get('y', 1.0)
2278
+ cubic_beta = cubic_params.get('beta', 0.5)
2279
+
2280
+ # Create Dash-style visualization from previous data
2281
+ st.info("Displaying previous analysis results. Adjust parameters and click 'Generate Analysis' to create a new visualization.")
2282
+
2283
+ dash_fig = create_dash_style_visualization(result, cubic_a, cubic_y, cubic_beta)
2284
+ st.plotly_chart(dash_fig, use_container_width=True)
2285
+
2286
+ # Create sub-tabs for additional visualizations
2287
+ pattern_tab, complex_tab = st.tabs(["Root Pattern Analysis", "Complex Plane View"])
2288
+
2289
+ # Root pattern visualization
2290
+ with pattern_tab:
2291
+ pattern_fig = create_root_pattern_visualization(result)
2292
+ st.plotly_chart(pattern_fig, use_container_width=True)
2293
+
2294
+ # Complex plane visualization
2295
+ with complex_tab:
2296
+ # Slider for selecting z value
2297
+ z_idx = st.slider(
2298
+ "Select z index",
2299
+ min_value=0,
2300
+ max_value=len(result['z_values'])-1,
2301
+ value=len(result['z_values'])//2,
2302
+ help="Select a specific z value to visualize its roots in the complex plane"
2303
+ )
2304
+
2305
+ # Create complex plane visualization
2306
+ complex_fig = create_complex_plane_visualization(result, z_idx)
2307
+ st.plotly_chart(complex_fig, use_container_width=True)
2308
+
2309
+ except Exception as e:
2310
+ st.info("πŸ‘ˆ Set parameters and click 'Generate Im(s) vs z Analysis' to create a visualization.")
2311
+ st.error(f"Error loading previous data: {str(e)}")
2312
+ else:
2313
+ # Show placeholder
2314
+ st.info("πŸ‘ˆ Set parameters and click 'Generate Im(s) vs z Analysis' to create a visualization.")
2315
+
2316
+ st.markdown('</div>', unsafe_allow_html=True)
2317
+
2318
+ # Add footer with instructions
2319
+ st.markdown("""
2320
+ <div class="footer">
2321
+ <h3>About the Matrix Analysis Dashboard</h3>
2322
+ <p>This dashboard performs two types of analyses using different computational approaches:</p>
2323
+ <ol>
2324
+ <li><strong>Eigenvalue Analysis (C++):</strong> Uses C++ with OpenCV for high-performance computation of eigenvalues of random matrices.</li>
2325
+ <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>
2326
+ </ol>
2327
+ <p>This hybrid approach combines C++'s performance for data-intensive calculations with SymPy's high-precision symbolic mathematics for accurate root finding.</p>
2328
+ </div>
2329
+ """, unsafe_allow_html=True)