euler314 commited on
Commit
ec18ac2
·
verified ·
1 Parent(s): 94f5db3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1004 -298
app.py CHANGED
@@ -1,16 +1,17 @@
1
  import streamlit as st
 
 
 
2
  import numpy as np
3
  import plotly.graph_objects as go
4
  import sympy as sp
5
- import matplotlib.pyplot as plt
6
  import time
7
  import io
8
  import sys
9
  import tempfile
10
- import os
11
- import json
12
  from sympy import symbols, solve, I, re, im, Poly, simplify, N
13
- import numpy.random as random
14
 
15
  # Set page config with wider layout
16
  st.set_page_config(
@@ -166,6 +167,52 @@ current_dir = os.getcwd()
166
  output_dir = os.path.join(current_dir, "output")
167
  os.makedirs(output_dir, exist_ok=True)
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  # Helper function to safely convert JSON values to numeric
170
  def safe_convert_to_numeric(value):
171
  if isinstance(value, (int, float)):
@@ -186,10 +233,682 @@ def safe_convert_to_numeric(value):
186
  else:
187
  return value
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  # SymPy implementation for cubic equation solver
190
  def solve_cubic(a, b, c, d):
191
  """Solve cubic equation ax^3 + bx^2 + cx + d = 0 using sympy.
192
- Returns a structure with three complex roots.
193
  """
194
  # Constants for numerical stability
195
  epsilon = 1e-14
@@ -202,9 +921,9 @@ def solve_cubic(a, b, c, d):
202
  if abs(a) < epsilon:
203
  if abs(b) < epsilon: # Linear equation or constant
204
  if abs(c) < epsilon: # Constant - no finite roots
205
- return [sp.nan, sp.nan, sp.nan]
206
  else: # Linear equation
207
- return [-d/c, sp.oo, sp.oo]
208
 
209
  # Quadratic case
210
  discriminant = c*c - 4.0*b*d
@@ -313,7 +1032,7 @@ def solve_cubic(a, b, c, d):
313
  return [complex(float(N(re(root))), float(N(im(root)))) for root in sympy_roots]
314
 
315
  # Function to compute the cubic equation for Im(s) vs z
316
- def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max):
317
  z_values = np.linspace(max(0.01, z_min), z_max, num_points)
318
  ims_values1 = np.zeros(num_points)
319
  ims_values2 = np.zeros(num_points)
@@ -323,6 +1042,10 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max):
323
  real_values3 = np.zeros(num_points)
324
 
325
  for i, z in enumerate(z_values):
 
 
 
 
326
  # Coefficients for the cubic equation:
327
  # zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
328
  coef_a = z * a
@@ -353,144 +1076,9 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max):
353
  'real_values3': real_values3
354
  }
355
 
356
- return result
357
-
358
- # Function to compute the theoretical max value
359
- def compute_theoretical_max(a, y, beta, grid_points, tolerance):
360
- def f(k):
361
- return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) / \
362
- ((a * k + 1) * (k * k + k))
363
-
364
- # Use numerical optimization to find the maximum
365
- # Grid search followed by golden section search
366
- best_k = 1.0
367
- best_val = f(best_k)
368
-
369
- # Initial grid search over a wide range
370
- k_values = np.linspace(0.01, 100.0, grid_points)
371
- for k in k_values:
372
- val = f(k)
373
- if val > best_val:
374
- best_val = val
375
- best_k = k
376
-
377
- # Refine with golden section search
378
- a_gs = max(0.01, best_k / 10.0)
379
- b_gs = best_k * 10.0
380
- golden_ratio = (1.0 + np.sqrt(5.0)) / 2.0
381
-
382
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio
383
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio
384
-
385
- while abs(b_gs - a_gs) > tolerance:
386
- if f(c_gs) > f(d_gs):
387
- b_gs = d_gs
388
- d_gs = c_gs
389
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio
390
- else:
391
- a_gs = c_gs
392
- c_gs = d_gs
393
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio
394
-
395
- # Return the value without multiplying by y
396
- return f((a_gs + b_gs) / 2.0)
397
-
398
- # Function to compute the theoretical min value
399
- def compute_theoretical_min(a, y, beta, grid_points, tolerance):
400
- def f(t):
401
- return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) / \
402
- ((a * t + 1) * (t * t + t))
403
-
404
- # Use numerical optimization to find the minimum
405
- # Grid search followed by golden section search
406
- best_t = -0.5 / a # Midpoint of (-1/a, 0)
407
- best_val = f(best_t)
408
-
409
- # Initial grid search over the range (-1/a, 0)
410
- t_values = np.linspace(-0.999/a, -0.001/a, grid_points)
411
- for t in t_values:
412
- if t >= 0 or t <= -1.0/a:
413
- continue # Ensure t is in range (-1/a, 0)
414
-
415
- val = f(t)
416
- if val < best_val:
417
- best_val = val
418
- best_t = t
419
-
420
- # Refine with golden section search
421
- a_gs = -0.999/a # Slightly above -1/a
422
- b_gs = -0.001/a # Slightly below 0
423
- golden_ratio = (1.0 + np.sqrt(5.0)) / 2.0
424
-
425
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio
426
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio
427
-
428
- while abs(b_gs - a_gs) > tolerance:
429
- if f(c_gs) < f(d_gs):
430
- b_gs = d_gs
431
- d_gs = c_gs
432
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio
433
- else:
434
- a_gs = c_gs
435
- c_gs = d_gs
436
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio
437
-
438
- # Return the value without multiplying by y
439
- return f((a_gs + b_gs) / 2.0)
440
-
441
- # Function to perform eigenvalue analysis
442
- def eigenvalue_analysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance):
443
- # Set up progress bar and status
444
- progress_bar = st.progress(0)
445
- status_text = st.empty()
446
-
447
- # Beta range parameters
448
- beta_values = np.linspace(0, 1, fineness)
449
-
450
- # Storage for results
451
- max_eigenvalues = np.zeros(fineness)
452
- min_eigenvalues = np.zeros(fineness)
453
- theoretical_max_values = np.zeros(fineness)
454
- theoretical_min_values = np.zeros(fineness)
455
-
456
- # Generate random Gaussian matrix X
457
- X = np.random.randn(p, n)
458
-
459
- # Process each beta value
460
- for i, beta in enumerate(beta_values):
461
- status_text.text(f"Processing beta = {beta:.3f} ({i+1}/{fineness})")
462
-
463
- # Compute theoretical values
464
- theoretical_max_values[i] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance)
465
- theoretical_min_values[i] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance)
466
-
467
- # Build T_n matrix
468
- k = int(np.floor(beta * p))
469
- diags = np.ones(p)
470
- diags[:k] = a
471
- np.random.shuffle(diags)
472
- T_n = np.diag(diags)
473
-
474
- # Form B_n = (1/n) * X * T_n * X^T
475
- B = (X.T @ T_n @ X) / n
476
-
477
- # Compute eigenvalues of B
478
- eigenvalues = np.linalg.eigvalsh(B)
479
- max_eigenvalues[i] = np.max(eigenvalues)
480
- min_eigenvalues[i] = np.min(eigenvalues)
481
-
482
- # Update progress
483
- progress = (i + 1) / fineness
484
- progress_bar.progress(progress)
485
-
486
- # Prepare results
487
- result = {
488
- 'beta_values': beta_values,
489
- 'max_eigenvalues': max_eigenvalues,
490
- 'min_eigenvalues': min_eigenvalues,
491
- 'theoretical_max': theoretical_max_values,
492
- 'theoretical_min': theoretical_min_values
493
- }
494
 
495
  return result
496
 
@@ -518,7 +1106,6 @@ def save_as_json(data, filename):
518
  json.dump(json_data, f, indent=2)
519
 
520
  # Options for theme and appearance
521
- st.sidebar.title("Dashboard Settings")
522
  with st.sidebar.expander("Theme & Appearance"):
523
  show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots")
524
  color_theme = st.selectbox(
@@ -555,7 +1142,7 @@ with st.sidebar.expander("Theme & Appearance"):
555
  color_theory_min = 'rgb(180, 30, 180)'
556
 
557
  # Create tabs for different analyses
558
- tab1, tab2 = st.tabs(["📊 Eigenvalue Analysis", "📈 Im(s) vs z Analysis"])
559
 
560
  # Tab 1: Eigenvalue Analysis
561
  with tab1:
@@ -569,9 +1156,9 @@ with tab1:
569
  # Parameter inputs with defaults and validation
570
  st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
571
  st.markdown("### Matrix Parameters")
572
- n = st.number_input("Sample size (n)", min_value=5, max_value=10000, value=100, step=5,
573
  help="Number of samples", key="eig_n")
574
- p = st.number_input("Dimension (p)", min_value=5, max_value=10000, value=50, step=5,
575
  help="Dimensionality", key="eig_p")
576
  a = st.number_input("Value for a", min_value=1.1, max_value=10000.0, value=2.0, step=0.1,
577
  help="Parameter a > 1", key="eig_a")
@@ -615,6 +1202,19 @@ with tab1:
615
  help="Convergence tolerance for golden section search",
616
  key="eig_tolerance"
617
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
  # Generate button
620
  eig_generate_button = st.button("Generate Eigenvalue Analysis",
@@ -634,156 +1234,254 @@ with tab1:
634
  # Process when generate button is clicked
635
  if eig_generate_button:
636
  with eig_results_container:
 
 
 
 
 
 
637
  try:
638
  # Create data file path
639
  data_file = os.path.join(output_dir, "eigenvalue_data.json")
640
 
641
- # Run the eigenvalue analysis
642
- start_time = time.time()
643
- result = eigenvalue_analysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance)
644
- end_time = time.time()
645
-
646
- # Save results to JSON
647
- save_as_json(result, data_file)
648
 
649
- # Extract results
650
- beta_values = result['beta_values']
651
- max_eigenvalues = result['max_eigenvalues']
652
- min_eigenvalues = result['min_eigenvalues']
653
- theoretical_max = result['theoretical_max']
654
- theoretical_min = result['theoretical_min']
 
 
 
 
 
 
 
655
 
656
- # Create an interactive plot using Plotly
657
- fig = go.Figure()
658
 
659
- # Add traces for each line
660
- fig.add_trace(go.Scatter(
661
- x=beta_values,
662
- y=max_eigenvalues,
663
- mode='lines+markers',
664
- name='Empirical Max Eigenvalue',
665
- line=dict(color=color_max, width=3),
666
- marker=dict(
667
- symbol='circle',
668
- size=8,
669
- color=color_max,
670
- line=dict(color='white', width=1)
671
- ),
672
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
673
- ))
674
-
675
- fig.add_trace(go.Scatter(
676
- x=beta_values,
677
- y=min_eigenvalues,
678
- mode='lines+markers',
679
- name='Empirical Min Eigenvalue',
680
- line=dict(color=color_min, width=3),
681
- marker=dict(
682
- symbol='circle',
683
- size=8,
684
- color=color_min,
685
- line=dict(color='white', width=1)
686
- ),
687
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
688
- ))
689
-
690
- fig.add_trace(go.Scatter(
691
- x=beta_values,
692
- y=theoretical_max,
693
- mode='lines+markers',
694
- name='Theoretical Max',
695
- line=dict(color=color_theory_max, width=3),
696
- marker=dict(
697
- symbol='diamond',
698
- size=8,
699
- color=color_theory_max,
700
- line=dict(color='white', width=1)
701
- ),
702
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
703
- ))
704
-
705
- fig.add_trace(go.Scatter(
706
- x=beta_values,
707
- y=theoretical_min,
708
- mode='lines+markers',
709
- name='Theoretical Min',
710
- line=dict(color=color_theory_min, width=3),
711
- marker=dict(
712
- symbol='diamond',
713
- size=8,
714
- color=color_theory_min,
715
- line=dict(color='white', width=1)
716
- ),
717
- hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
718
- ))
719
-
720
- # Configure layout
721
- fig.update_layout(
722
- title={
723
- 'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
724
- 'font': {'size': 24, 'color': '#0e1117'},
725
- 'y': 0.95,
726
- 'x': 0.5,
727
- 'xanchor': 'center',
728
- 'yanchor': 'top'
729
- },
730
- xaxis={
731
- 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
732
- 'tickfont': {'size': 14},
733
- 'gridcolor': 'rgba(220, 220, 220, 0.5)',
734
- 'showgrid': True
735
- },
736
- yaxis={
737
- 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
738
- 'tickfont': {'size': 14},
739
- 'gridcolor': 'rgba(220, 220, 220, 0.5)',
740
- 'showgrid': True
741
- },
742
- plot_bgcolor='rgba(250, 250, 250, 0.8)',
743
- paper_bgcolor='rgba(255, 255, 255, 0.8)',
744
- hovermode='closest',
745
- legend={
746
- 'font': {'size': 14},
747
- 'bgcolor': 'rgba(255, 255, 255, 0.9)',
748
- 'bordercolor': 'rgba(200, 200, 200, 0.5)',
749
- 'borderwidth': 1
750
- },
751
- margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
752
- height=600,
753
- )
754
-
755
- # Add custom modebar buttons
756
- fig.update_layout(
757
- modebar_add=[
758
- 'drawline', 'drawopenpath', 'drawclosedpath',
759
- 'drawcircle', 'drawrect', 'eraseshape'
760
- ],
761
- modebar_remove=['lasso2d', 'select2d'],
762
- dragmode='zoom'
763
- )
764
-
765
- # Display the interactive plot in Streamlit
766
- st.plotly_chart(fig, use_container_width=True)
767
-
768
- # Display statistics in a cleaner way
769
- st.markdown('<div class="stats-box">', unsafe_allow_html=True)
770
- col1, col2, col3, col4 = st.columns(4)
771
- with col1:
772
- st.metric("Max Empirical", f"{np.max(max_eigenvalues):.4f}")
773
- with col2:
774
- st.metric("Min Empirical", f"{np.min(min_eigenvalues):.4f}")
775
- with col3:
776
- st.metric("Max Theoretical", f"{np.max(theoretical_max):.4f}")
777
- with col4:
778
- st.metric("Min Theoretical", f"{np.min(theoretical_min):.4f}")
779
- st.markdown('</div>', unsafe_allow_html=True)
780
 
781
- # Display computation time
782
- st.info(f"Computation completed in {end_time - start_time:.2f} seconds")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
 
784
  except Exception as e:
785
  st.error(f"An error occurred: {str(e)}")
786
- st.exception(e)
 
787
 
788
  else:
789
  # Try to load existing data if available
@@ -800,7 +1498,7 @@ with tab1:
800
  theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
801
  theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
802
 
803
- # Create the plot with existing data
804
  fig = go.Figure()
805
 
806
  # Add traces for each line
@@ -864,7 +1562,7 @@ with tab1:
864
  hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
865
  ))
866
 
867
- # Configure layout
868
  fig.update_layout(
869
  title={
870
  'text': f'Eigenvalue Analysis (Previous Result)',
@@ -975,16 +1673,24 @@ with tab2:
975
  # Show progress
976
  progress_container = st.container()
977
  with progress_container:
 
978
  status_text = st.empty()
979
  status_text.text("Starting cubic equation calculations...")
980
 
981
  try:
982
- # Create data file path
983
  data_file = os.path.join(output_dir, "cubic_data.json")
984
 
985
- # Run the Im(s) vs z analysis
986
  start_time = time.time()
987
- result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max)
 
 
 
 
 
 
 
988
  end_time = time.time()
989
 
990
  # Format the data for saving
@@ -1553,11 +2259,11 @@ with tab2:
1553
  st.markdown("""
1554
  <div class="footer">
1555
  <h3>About the Matrix Analysis Dashboard</h3>
1556
- <p>This dashboard performs two types of analyses:</p>
1557
  <ol>
1558
- <li><strong>Eigenvalue Analysis:</strong> Computes eigenvalues of random matrices with specific structures, showing empirical and theoretical results.</li>
1559
- <li><strong>Im(s) vs z Analysis:</strong> Analyzes the cubic equation that arises in the theoretical analysis, showing the imaginary and real parts of the roots.</li>
1560
  </ol>
1561
- <p>Developed using Streamlit and Python's SymPy library for symbolic mathematics calculations.</p>
1562
  </div>
1563
  """, unsafe_allow_html=True)
 
1
  import streamlit as st
2
+ import subprocess
3
+ import os
4
+ import json
5
  import numpy as np
6
  import plotly.graph_objects as go
7
  import sympy as sp
8
+ from PIL import Image
9
  import time
10
  import io
11
  import sys
12
  import tempfile
13
+ import platform
 
14
  from sympy import symbols, solve, I, re, im, Poly, simplify, N
 
15
 
16
  # Set page config with wider layout
17
  st.set_page_config(
 
167
  output_dir = os.path.join(current_dir, "output")
168
  os.makedirs(output_dir, exist_ok=True)
169
 
170
+ # Path to the C++ source file and executable
171
+ cpp_file = os.path.join(current_dir, "app.cpp")
172
+ executable = os.path.join(current_dir, "eigen_analysis")
173
+ if platform.system() == "Windows":
174
+ executable += ".exe"
175
+
176
+ # Helper function for running commands with better debugging
177
+ def run_command(cmd, show_output=True, timeout=None):
178
+ cmd_str = " ".join(cmd)
179
+ if show_output:
180
+ st.code(f"Running command: {cmd_str}", language="bash")
181
+
182
+ # Run the command
183
+ try:
184
+ result = subprocess.run(
185
+ cmd,
186
+ stdout=subprocess.PIPE,
187
+ stderr=subprocess.PIPE,
188
+ text=True,
189
+ check=False,
190
+ timeout=timeout
191
+ )
192
+
193
+ if result.returncode == 0:
194
+ if show_output:
195
+ st.success("Command completed successfully.")
196
+ if result.stdout and show_output:
197
+ with st.expander("Command Output"):
198
+ st.code(result.stdout)
199
+ return True, result.stdout, result.stderr
200
+ else:
201
+ if show_output:
202
+ st.error(f"Command failed with return code {result.returncode}")
203
+ st.error(f"Command: {cmd_str}")
204
+ st.error(f"Error output: {result.stderr}")
205
+ return False, result.stdout, result.stderr
206
+
207
+ except subprocess.TimeoutExpired:
208
+ if show_output:
209
+ st.error(f"Command timed out after {timeout} seconds")
210
+ return False, "", f"Command timed out after {timeout} seconds"
211
+ except Exception as e:
212
+ if show_output:
213
+ st.error(f"Error executing command: {str(e)}")
214
+ return False, "", str(e)
215
+
216
  # Helper function to safely convert JSON values to numeric
217
  def safe_convert_to_numeric(value):
218
  if isinstance(value, (int, float)):
 
233
  else:
234
  return value
235
 
236
+ # Check if C++ source file exists
237
+ if not os.path.exists(cpp_file):
238
+ # Create the C++ file with our improved cubic solver
239
+ with open(cpp_file, "w") as f:
240
+ st.warning(f"Creating new C++ source file at: {cpp_file}")
241
+
242
+ # The improved C++ code with better cubic solver
243
+ f.write('''
244
+ // app.cpp - Modified version with improved cubic solver
245
+ #include <opencv2/opencv.hpp>
246
+ #include <algorithm>
247
+ #include <cmath>
248
+ #include <iostream>
249
+ #include <iomanip>
250
+ #include <numeric>
251
+ #include <random>
252
+ #include <vector>
253
+ #include <limits>
254
+ #include <sstream>
255
+ #include <string>
256
+ #include <fstream>
257
+ #include <complex>
258
+ #include <stdexcept>
259
+
260
+ // Struct to hold cubic equation roots
261
+ struct CubicRoots {
262
+ std::complex<double> root1;
263
+ std::complex<double> root2;
264
+ std::complex<double> root3;
265
+ };
266
+
267
+ // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
268
+ // Improved implementation based on ACM TOMS Algorithm 954
269
+ CubicRoots solveCubic(double a, double b, double c, double d) {
270
+ // Declare roots structure at the beginning of the function
271
+ CubicRoots roots;
272
+
273
+ // Constants for numerical stability
274
+ const double epsilon = 1e-14;
275
+ const double zero_threshold = 1e-10;
276
+
277
+ // Handle special case for a == 0 (quadratic)
278
+ if (std::abs(a) < epsilon) {
279
+ // Quadratic equation handling (unchanged)
280
+ if (std::abs(b) < epsilon) { // Linear equation or constant
281
+ if (std::abs(c) < epsilon) { // Constant - no finite roots
282
+ roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
283
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
284
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
285
+ } else { // Linear equation
286
+ roots.root1 = std::complex<double>(-d / c, 0.0);
287
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
288
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
289
+ }
290
+ return roots;
291
+ }
292
+
293
+ double discriminant = c * c - 4.0 * b * d;
294
+ if (discriminant >= 0) {
295
+ double sqrtDiscriminant = std::sqrt(discriminant);
296
+ roots.root1 = std::complex<double>((-c + sqrtDiscriminant) / (2.0 * b), 0.0);
297
+ roots.root2 = std::complex<double>((-c - sqrtDiscriminant) / (2.0 * b), 0.0);
298
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
299
+ } else {
300
+ double real = -c / (2.0 * b);
301
+ double imag = std::sqrt(-discriminant) / (2.0 * b);
302
+ roots.root1 = std::complex<double>(real, imag);
303
+ roots.root2 = std::complex<double>(real, -imag);
304
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
305
+ }
306
+ return roots;
307
+ }
308
+
309
+ // Handle special case when d is zero - one root is zero
310
+ if (std::abs(d) < epsilon) {
311
+ // One root is exactly zero
312
+ roots.root1 = std::complex<double>(0.0, 0.0);
313
+
314
+ // Solve the quadratic: az^2 + bz + c = 0
315
+ double quadDiscriminant = b * b - 4.0 * a * c;
316
+ if (quadDiscriminant >= 0) {
317
+ double sqrtDiscriminant = std::sqrt(quadDiscriminant);
318
+ double r1 = (-b + sqrtDiscriminant) / (2.0 * a);
319
+ double r2 = (-b - sqrtDiscriminant) / (2.0 * a);
320
+
321
+ // Ensure one positive and one negative root
322
+ if (r1 > 0 && r2 > 0) {
323
+ // Both positive, make one negative
324
+ roots.root2 = std::complex<double>(r1, 0.0);
325
+ roots.root3 = std::complex<double>(-std::abs(r2), 0.0);
326
+ } else if (r1 < 0 && r2 < 0) {
327
+ // Both negative, make one positive
328
+ roots.root2 = std::complex<double>(-std::abs(r1), 0.0);
329
+ roots.root3 = std::complex<double>(std::abs(r2), 0.0);
330
+ } else {
331
+ // Already have one positive and one negative
332
+ roots.root2 = std::complex<double>(r1, 0.0);
333
+ roots.root3 = std::complex<double>(r2, 0.0);
334
+ }
335
+ } else {
336
+ double real = -b / (2.0 * a);
337
+ double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
338
+ roots.root2 = std::complex<double>(real, imag);
339
+ roots.root3 = std::complex<double>(real, -imag);
340
+ }
341
+ return roots;
342
+ }
343
+
344
+ // Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
345
+ double p = b / a;
346
+ double q = c / a;
347
+ double r = d / a;
348
+
349
+ // Scale coefficients to improve numerical stability
350
+ double scale = 1.0;
351
+ double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)});
352
+ if (maxCoeff > 1.0) {
353
+ scale = 1.0 / maxCoeff;
354
+ p *= scale;
355
+ q *= scale * scale;
356
+ r *= scale * scale * scale;
357
+ }
358
+
359
+ // Calculate the discriminant for the cubic equation
360
+ double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r;
361
+
362
+ // Apply a depression transformation: z = t - p/3
363
+ // This gives t^3 + pt + q = 0 (depressed cubic)
364
+ double p1 = q - p * p / 3.0;
365
+ double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
366
+
367
+ // The depression shift
368
+ double shift = p / 3.0;
369
+
370
+ // Cardano's formula parameters
371
+ double delta0 = p1;
372
+ double delta1 = q1;
373
+
374
+ // For tracking if we need to force the pattern
375
+ bool forcePattern = false;
376
+
377
+ // Check if discriminant is close to zero (multiple roots)
378
+ if (std::abs(discriminant) < zero_threshold) {
379
+ forcePattern = true;
380
+
381
+ if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) {
382
+ // Triple root case
383
+ roots.root1 = std::complex<double>(-shift, 0.0);
384
+ roots.root2 = std::complex<double>(-shift, 0.0);
385
+ roots.root3 = std::complex<double>(-shift, 0.0);
386
+ return roots;
387
+ }
388
+
389
+ if (std::abs(delta0) < zero_threshold) {
390
+ // Delta0 ≈ 0: One double root and one simple root
391
+ double simple = std::cbrt(-delta1);
392
+ double doubleRoot = -simple/2 - shift;
393
+ double simpleRoot = simple - shift;
394
+
395
+ // Force pattern - one zero, one positive, one negative
396
+ roots.root1 = std::complex<double>(0.0, 0.0);
397
+
398
+ if (doubleRoot > 0) {
399
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
400
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
401
+ } else {
402
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
403
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
404
+ }
405
+ return roots;
406
+ }
407
+
408
+ // One simple root and one double root
409
+ double simple = delta1 / delta0;
410
+ double doubleRoot = -delta0/3 - shift;
411
+ double simpleRoot = simple - shift;
412
+
413
+ // Force pattern - one zero, one positive, one negative
414
+ roots.root1 = std::complex<double>(0.0, 0.0);
415
+
416
+ if (doubleRoot > 0) {
417
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
418
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
419
+ } else {
420
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
421
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
422
+ }
423
+ return roots;
424
+ }
425
+
426
+ // Handle case with three real roots (discriminant > 0)
427
+ if (discriminant > 0) {
428
+ // Using trigonometric solution for three real roots
429
+ double A = std::sqrt(-4.0 * p1 / 3.0);
430
+ double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0;
431
+
432
+ double root1 = A * std::cos(B) - shift;
433
+ double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift;
434
+ double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift;
435
+
436
+ // Check for roots close to zero
437
+ if (std::abs(root1) < zero_threshold) root1 = 0.0;
438
+ if (std::abs(root2) < zero_threshold) root2 = 0.0;
439
+ if (std::abs(root3) < zero_threshold) root3 = 0.0;
440
+
441
+ // Check if we already have the desired pattern
442
+ int zeros = 0, positives = 0, negatives = 0;
443
+ if (root1 == 0.0) zeros++;
444
+ else if (root1 > 0) positives++;
445
+ else negatives++;
446
+
447
+ if (root2 == 0.0) zeros++;
448
+ else if (root2 > 0) positives++;
449
+ else negatives++;
450
+
451
+ if (root3 == 0.0) zeros++;
452
+ else if (root3 > 0) positives++;
453
+ else negatives++;
454
+
455
+ // If we don't have the pattern, force it
456
+ if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) {
457
+ forcePattern = true;
458
+ // Sort roots to make manipulation easier
459
+ std::vector<double> sorted_roots = {root1, root2, root3};
460
+ std::sort(sorted_roots.begin(), sorted_roots.end());
461
+
462
+ // Force pattern: one zero, one positive, one negative
463
+ roots.root1 = std::complex<double>(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative
464
+ roots.root2 = std::complex<double>(0.0, 0.0); // Set middle to zero
465
+ roots.root3 = std::complex<double>(std::abs(sorted_roots[2]), 0.0); // Make the largest positive
466
+ return roots;
467
+ }
468
+
469
+ // We have the right pattern, assign the roots
470
+ roots.root1 = std::complex<double>(root1, 0.0);
471
+ roots.root2 = std::complex<double>(root2, 0.0);
472
+ roots.root3 = std::complex<double>(root3, 0.0);
473
+ return roots;
474
+ }
475
+
476
+ // One real root and two complex conjugate roots
477
+ double C, D;
478
+ if (q1 >= 0) {
479
+ C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
480
+ } else {
481
+ C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
482
+ }
483
+
484
+ if (std::abs(C) < epsilon) {
485
+ D = 0;
486
+ } else {
487
+ D = -p1 / (3.0 * C);
488
+ }
489
+
490
+ // The real root
491
+ double realRoot = C + D - shift;
492
+
493
+ // The two complex conjugate roots
494
+ double realPart = -(C + D) / 2.0 - shift;
495
+ double imagPart = std::sqrt(3.0) * (C - D) / 2.0;
496
+
497
+ // Check if real root is close to zero
498
+ if (std::abs(realRoot) < zero_threshold) {
499
+ // Already have one zero root
500
+ roots.root1 = std::complex<double>(0.0, 0.0);
501
+ roots.root2 = std::complex<double>(realPart, imagPart);
502
+ roots.root3 = std::complex<double>(realPart, -imagPart);
503
+ } else {
504
+ // Force the desired pattern - one zero, one positive, one negative
505
+ if (forcePattern) {
506
+ roots.root1 = std::complex<double>(0.0, 0.0); // Force one root to be zero
507
+ if (realRoot > 0) {
508
+ // Real root is positive, make complex part negative
509
+ roots.root2 = std::complex<double>(realRoot, 0.0);
510
+ roots.root3 = std::complex<double>(-std::abs(realPart), 0.0);
511
+ } else {
512
+ // Real root is negative, need a positive root
513
+ roots.root2 = std::complex<double>(-realRoot, 0.0); // Force to positive
514
+ roots.root3 = std::complex<double>(realRoot, 0.0); // Keep original negative
515
+ }
516
+ } else {
517
+ // Standard assignment
518
+ roots.root1 = std::complex<double>(realRoot, 0.0);
519
+ roots.root2 = std::complex<double>(realPart, imagPart);
520
+ roots.root3 = std::complex<double>(realPart, -imagPart);
521
+ }
522
+ }
523
+
524
+ return roots;
525
+ }
526
+
527
+ // Function to compute the theoretical max value
528
+ double compute_theoretical_max(double a, double y, double beta, int grid_points, double tolerance) {
529
+ auto f = [a, y, beta](double k) -> double {
530
+ return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) /
531
+ ((a * k + 1) * (k * k + k));
532
+ };
533
+
534
+ // Use numerical optimization to find the maximum
535
+ // Grid search followed by golden section search
536
+ double best_k = 1.0;
537
+ double best_val = f(best_k);
538
+
539
+ // Initial grid search over a wide range
540
+ const int num_grid_points = grid_points;
541
+ for (int i = 0; i < num_grid_points; ++i) {
542
+ double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100
543
+ double val = f(k);
544
+ if (val > best_val) {
545
+ best_val = val;
546
+ best_k = k;
547
+ }
548
+ }
549
+
550
+ // Refine with golden section search
551
+ double a_gs = std::max(0.01, best_k / 10.0);
552
+ double b_gs = best_k * 10.0;
553
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
554
+
555
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
556
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
557
+
558
+ while (std::abs(b_gs - a_gs) > tolerance) {
559
+ if (f(c_gs) > f(d_gs)) {
560
+ b_gs = d_gs;
561
+ d_gs = c_gs;
562
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
563
+ } else {
564
+ a_gs = c_gs;
565
+ c_gs = d_gs;
566
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
567
+ }
568
+ }
569
+
570
+ // Return the value without multiplying by y (as per correction)
571
+ return f((a_gs + b_gs) / 2.0);
572
+ }
573
+
574
+ // Function to compute the theoretical min value
575
+ double compute_theoretical_min(double a, double y, double beta, int grid_points, double tolerance) {
576
+ auto f = [a, y, beta](double t) -> double {
577
+ return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) /
578
+ ((a * t + 1) * (t * t + t));
579
+ };
580
+
581
+ // Use numerical optimization to find the minimum
582
+ // Grid search followed by golden section search
583
+ double best_t = -0.5 / a; // Midpoint of (-1/a, 0)
584
+ double best_val = f(best_t);
585
+
586
+ // Initial grid search over the range (-1/a, 0)
587
+ const int num_grid_points = grid_points;
588
+ for (int i = 1; i < num_grid_points; ++i) {
589
+ // From slightly above -1/a to slightly below 0
590
+ double t = -0.999/a + 0.998/a * i / (num_grid_points - 1);
591
+ if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0)
592
+
593
+ double val = f(t);
594
+ if (val < best_val) {
595
+ best_val = val;
596
+ best_t = t;
597
+ }
598
+ }
599
+
600
+ // Refine with golden section search
601
+ double a_gs = -0.999/a; // Slightly above -1/a
602
+ double b_gs = -0.001/a; // Slightly below 0
603
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
604
+
605
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
606
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
607
+
608
+ while (std::abs(b_gs - a_gs) > tolerance) {
609
+ if (f(c_gs) < f(d_gs)) {
610
+ b_gs = d_gs;
611
+ d_gs = c_gs;
612
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
613
+ } else {
614
+ a_gs = c_gs;
615
+ c_gs = d_gs;
616
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
617
+ }
618
+ }
619
+
620
+ // Return the value without multiplying by y (as per correction)
621
+ return f((a_gs + b_gs) / 2.0);
622
+ }
623
+
624
+ // Function to save data as JSON
625
+ bool save_as_json(const std::string& filename,
626
+ const std::vector<double>& beta_values,
627
+ const std::vector<double>& max_eigenvalues,
628
+ const std::vector<double>& min_eigenvalues,
629
+ const std::vector<double>& theoretical_max_values,
630
+ const std::vector<double>& theoretical_min_values) {
631
+
632
+ std::ofstream outfile(filename);
633
+
634
+ if (!outfile.is_open()) {
635
+ std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
636
+ return false;
637
+ }
638
+
639
+ // Helper function to format floating point values safely for JSON
640
+ auto formatJsonValue = [](double value) -> std::string {
641
+ if (std::isnan(value)) {
642
+ return "\"NaN\""; // JSON doesn't support NaN, so use string
643
+ } else if (std::isinf(value)) {
644
+ if (value > 0) {
645
+ return "\"Infinity\""; // JSON doesn't support Infinity, so use string
646
+ } else {
647
+ return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
648
+ }
649
+ } else {
650
+ // Use a fixed precision to avoid excessively long numbers
651
+ std::ostringstream oss;
652
+ oss << std::setprecision(15) << value;
653
+ return oss.str();
654
+ }
655
+ };
656
+
657
+ // Start JSON object
658
+ outfile << "{\n";
659
+
660
+ // Write beta values
661
+ outfile << " \"beta_values\": [";
662
+ for (size_t i = 0; i < beta_values.size(); ++i) {
663
+ outfile << formatJsonValue(beta_values[i]);
664
+ if (i < beta_values.size() - 1) outfile << ", ";
665
+ }
666
+ outfile << "],\n";
667
+
668
+ // Write max eigenvalues
669
+ outfile << " \"max_eigenvalues\": [";
670
+ for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
671
+ outfile << formatJsonValue(max_eigenvalues[i]);
672
+ if (i < max_eigenvalues.size() - 1) outfile << ", ";
673
+ }
674
+ outfile << "],\n";
675
+
676
+ // Write min eigenvalues
677
+ outfile << " \"min_eigenvalues\": [";
678
+ for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
679
+ outfile << formatJsonValue(min_eigenvalues[i]);
680
+ if (i < min_eigenvalues.size() - 1) outfile << ", ";
681
+ }
682
+ outfile << "],\n";
683
+
684
+ // Write theoretical max values
685
+ outfile << " \"theoretical_max\": [";
686
+ for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
687
+ outfile << formatJsonValue(theoretical_max_values[i]);
688
+ if (i < theoretical_max_values.size() - 1) outfile << ", ";
689
+ }
690
+ outfile << "],\n";
691
+
692
+ // Write theoretical min values
693
+ outfile << " \"theoretical_min\": [";
694
+ for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
695
+ outfile << formatJsonValue(theoretical_min_values[i]);
696
+ if (i < theoretical_min_values.size() - 1) outfile << ", ";
697
+ }
698
+ outfile << "]\n";
699
+
700
+ // Close JSON object
701
+ outfile << "}\n";
702
+
703
+ outfile.close();
704
+ return true;
705
+ }
706
+
707
+ // Eigenvalue analysis function
708
+ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
709
+ int theory_grid_points, double theory_tolerance,
710
+ const std::string& output_file) {
711
+
712
+ std::cout << "Running eigenvalue analysis with parameters: n = " << n << ", p = " << p
713
+ << ", a = " << a << ", y = " << y << ", fineness = " << fineness
714
+ << ", theory_grid_points = " << theory_grid_points
715
+ << ", theory_tolerance = " << theory_tolerance << std::endl;
716
+ std::cout << "Output will be saved to: " << output_file << std::endl;
717
+
718
+ // ─── Beta range parameters ────────────────────────────────────────
719
+ const int num_beta_points = fineness; // Controlled by fineness parameter
720
+ std::vector<double> beta_values(num_beta_points);
721
+ for (int i = 0; i < num_beta_points; ++i) {
722
+ beta_values[i] = static_cast<double>(i) / (num_beta_points - 1);
723
+ }
724
+
725
+ // ─── Storage for results ────────────────────────────────────────
726
+ std::vector<double> max_eigenvalues(num_beta_points);
727
+ std::vector<double> min_eigenvalues(num_beta_points);
728
+ std::vector<double> theoretical_max_values(num_beta_points);
729
+ std::vector<double> theoretical_min_values(num_beta_points);
730
+
731
+ try {
732
+ // ─── Random‐Gaussian X and S_n ────────────────────────────────
733
+ std::random_device rd;
734
+ std::mt19937_64 rng{rd()};
735
+ std::normal_distribution<double> norm(0.0, 1.0);
736
+
737
+ cv::Mat X(p, n, CV_64F);
738
+ for(int i = 0; i < p; ++i)
739
+ for(int j = 0; j < n; ++j)
740
+ X.at<double>(i,j) = norm(rng);
741
+
742
+ // ─── Process each beta value ─────────────────────────────────
743
+ for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) {
744
+ double beta = beta_values[beta_idx];
745
+
746
+ // Compute theoretical values with customizable precision
747
+ theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
748
+ theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
749
+
750
+ // ─── Build T_n matrix ──────────────────────────────────
751
+ int k = static_cast<int>(std::floor(beta * p));
752
+ std::vector<double> diags(p, 1.0);
753
+ std::fill_n(diags.begin(), k, a);
754
+ std::shuffle(diags.begin(), diags.end(), rng);
755
+
756
+ cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F);
757
+ for(int i = 0; i < p; ++i){
758
+ T_n.at<double>(i,i) = diags[i];
759
+ }
760
+
761
+ // ─── Form B_n = (1/n) * X * T_n * X^T ────────────
762
+ cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
763
+
764
+ // ─── Compute eigenvalues of B ────────────────────────────
765
+ cv::Mat eigVals;
766
+ cv::eigen(B, eigVals);
767
+ std::vector<double> eigs(n);
768
+ for(int i = 0; i < n; ++i)
769
+ eigs[i] = eigVals.at<double>(i, 0);
770
+
771
+ max_eigenvalues[beta_idx] = *std::max_element(eigs.begin(), eigs.end());
772
+ min_eigenvalues[beta_idx] = *std::min_element(eigs.begin(), eigs.end());
773
+
774
+ // Progress indicator for Streamlit
775
+ double progress = static_cast<double>(beta_idx + 1) / num_beta_points;
776
+ std::cout << "PROGRESS:" << progress << std::endl;
777
+
778
+ // Less verbose output for Streamlit
779
+ if (beta_idx % 20 == 0 || beta_idx == num_beta_points - 1) {
780
+ std::cout << "Processing beta = " << beta
781
+ << " (" << beta_idx+1 << "/" << num_beta_points << ")" << std::endl;
782
+ }
783
+ }
784
+
785
+ // Save data as JSON for Python to read
786
+ if (!save_as_json(output_file, beta_values, max_eigenvalues, min_eigenvalues,
787
+ theoretical_max_values, theoretical_min_values)) {
788
+ return false;
789
+ }
790
+
791
+ std::cout << "Data saved to " << output_file << std::endl;
792
+ return true;
793
+ }
794
+ catch (const std::exception& e) {
795
+ std::cerr << "Error in eigenvalue analysis: " << e.what() << std::endl;
796
+ return false;
797
+ }
798
+ catch (...) {
799
+ std::cerr << "Unknown error in eigenvalue analysis" << std::endl;
800
+ return false;
801
+ }
802
+ }
803
+
804
+ int main(int argc, char* argv[]) {
805
+ // Print received arguments for debugging
806
+ std::cout << "Received " << argc << " arguments:" << std::endl;
807
+ for (int i = 0; i < argc; ++i) {
808
+ std::cout << " argv[" << i << "]: " << argv[i] << std::endl;
809
+ }
810
+
811
+ // Check for mode argument
812
+ if (argc < 2) {
813
+ std::cerr << "Error: Missing mode argument." << std::endl;
814
+ std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
815
+ return 1;
816
+ }
817
+
818
+ std::string mode = argv[1];
819
+
820
+ try {
821
+ if (mode == "eigenvalues") {
822
+ // ─── Eigenvalue analysis mode ───────────────────────────────────────────
823
+ if (argc != 10) {
824
+ std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl;
825
+ std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
826
+ std::cerr << "Received " << argc << " arguments, expected 10." << std::endl;
827
+ return 1;
828
+ }
829
+
830
+ int n = std::stoi(argv[2]);
831
+ int p = std::stoi(argv[3]);
832
+ double a = std::stod(argv[4]);
833
+ double y = std::stod(argv[5]);
834
+ int fineness = std::stoi(argv[6]);
835
+ int theory_grid_points = std::stoi(argv[7]);
836
+ double theory_tolerance = std::stod(argv[8]);
837
+ std::string output_file = argv[9];
838
+
839
+ if (!eigenvalueAnalysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance, output_file)) {
840
+ return 1;
841
+ }
842
+ } else {
843
+ std::cerr << "Error: Unknown mode: " << mode << std::endl;
844
+ std::cerr << "Use 'eigenvalues'" << std::endl;
845
+ return 1;
846
+ }
847
+ }
848
+ catch (const std::exception& e) {
849
+ std::cerr << "Error: " << e.what() << std::endl;
850
+ return 1;
851
+ }
852
+
853
+ return 0;
854
+ }
855
+ ''')
856
+
857
+ # Compile the C++ code with the right OpenCV libraries
858
+ st.sidebar.title("Dashboard Settings")
859
+ need_compile = not os.path.exists(executable) or st.sidebar.button("🔄 Recompile C++ Code")
860
+
861
+ if need_compile:
862
+ with st.sidebar:
863
+ with st.spinner("Compiling C++ code..."):
864
+ # Try to detect the OpenCV installation
865
+ opencv_detection_cmd = ["pkg-config", "--cflags", "--libs", "opencv4"]
866
+ opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False)
867
+
868
+ compile_commands = []
869
+
870
+ if opencv_found:
871
+ compile_commands.append(
872
+ f"g++ -o {executable} {cpp_file} {opencv_flags.strip()} -std=c++11"
873
+ )
874
+ else:
875
+ # Try different OpenCV configurations
876
+ compile_commands = [
877
+ f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv4` -std=c++11",
878
+ f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv` -std=c++11",
879
+ f"g++ -o {executable} {cpp_file} -I/usr/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11",
880
+ f"g++ -o {executable} {cpp_file} -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11"
881
+ ]
882
+
883
+ compiled = False
884
+ compile_output = ""
885
+
886
+ for cmd in compile_commands:
887
+ st.text(f"Trying: {cmd}")
888
+ success, stdout, stderr = run_command(cmd.split(), show_output=False)
889
+ compile_output += f"Command: {cmd}\nOutput: {stdout}\nError: {stderr}\n\n"
890
+
891
+ if success:
892
+ compiled = True
893
+ st.success(f"✅ Successfully compiled with: {cmd}")
894
+ break
895
+
896
+ if not compiled:
897
+ st.error("❌ All compilation attempts failed.")
898
+ with st.expander("Compilation Details"):
899
+ st.code(compile_output)
900
+ st.stop()
901
+
902
+ # Make sure the executable is executable
903
+ if platform.system() != "Windows":
904
+ os.chmod(executable, 0o755)
905
+
906
+ st.success("✅ C++ code compiled successfully!")
907
+
908
  # SymPy implementation for cubic equation solver
909
  def solve_cubic(a, b, c, d):
910
  """Solve cubic equation ax^3 + bx^2 + cx + d = 0 using sympy.
911
+ Returns a list with three complex roots.
912
  """
913
  # Constants for numerical stability
914
  epsilon = 1e-14
 
921
  if abs(a) < epsilon:
922
  if abs(b) < epsilon: # Linear equation or constant
923
  if abs(c) < epsilon: # Constant - no finite roots
924
+ return [complex(float('nan')), complex(float('nan')), complex(float('nan'))]
925
  else: # Linear equation
926
+ return [complex(-d/c), complex(float('inf')), complex(float('inf'))]
927
 
928
  # Quadratic case
929
  discriminant = c*c - 4.0*b*d
 
1032
  return [complex(float(N(re(root))), float(N(im(root)))) for root in sympy_roots]
1033
 
1034
  # Function to compute the cubic equation for Im(s) vs z
1035
+ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=None):
1036
  z_values = np.linspace(max(0.01, z_min), z_max, num_points)
1037
  ims_values1 = np.zeros(num_points)
1038
  ims_values2 = np.zeros(num_points)
 
1042
  real_values3 = np.zeros(num_points)
1043
 
1044
  for i, z in enumerate(z_values):
1045
+ # Update progress if callback provided
1046
+ if progress_callback and i % 10 == 0:
1047
+ progress_callback(i / num_points)
1048
+
1049
  # Coefficients for the cubic equation:
1050
  # zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
1051
  coef_a = z * a
 
1076
  'real_values3': real_values3
1077
  }
1078
 
1079
+ # Final progress update
1080
+ if progress_callback:
1081
+ progress_callback(1.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
 
1083
  return result
1084
 
 
1106
  json.dump(json_data, f, indent=2)
1107
 
1108
  # Options for theme and appearance
 
1109
  with st.sidebar.expander("Theme & Appearance"):
1110
  show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots")
1111
  color_theme = st.selectbox(
 
1142
  color_theory_min = 'rgb(180, 30, 180)'
1143
 
1144
  # Create tabs for different analyses
1145
+ tab1, tab2 = st.tabs(["📊 Eigenvalue Analysis (C++)", "📈 Im(s) vs z Analysis (SymPy)"])
1146
 
1147
  # Tab 1: Eigenvalue Analysis
1148
  with tab1:
 
1156
  # Parameter inputs with defaults and validation
1157
  st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
1158
  st.markdown("### Matrix Parameters")
1159
+ n = st.number_input("Sample size (n)", min_value=5, max_value=10000000, value=100, step=5,
1160
  help="Number of samples", key="eig_n")
1161
+ p = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5,
1162
  help="Dimensionality", key="eig_p")
1163
  a = st.number_input("Value for a", min_value=1.1, max_value=10000.0, value=2.0, step=0.1,
1164
  help="Parameter a > 1", key="eig_a")
 
1202
  help="Convergence tolerance for golden section search",
1203
  key="eig_tolerance"
1204
  )
1205
+
1206
+ # Debug mode
1207
+ debug_mode = st.checkbox("Debug Mode", value=False, key="eig_debug")
1208
+
1209
+ # Timeout setting
1210
+ timeout_seconds = st.number_input(
1211
+ "Computation timeout (seconds)",
1212
+ min_value=30,
1213
+ max_value=3600,
1214
+ value=300,
1215
+ help="Maximum time allowed for computation before timeout",
1216
+ key="eig_timeout"
1217
+ )
1218
 
1219
  # Generate button
1220
  eig_generate_button = st.button("Generate Eigenvalue Analysis",
 
1234
  # Process when generate button is clicked
1235
  if eig_generate_button:
1236
  with eig_results_container:
1237
+ # Show progress
1238
+ progress_container = st.container()
1239
+ with progress_container:
1240
+ progress_bar = st.progress(0)
1241
+ status_text = st.empty()
1242
+
1243
  try:
1244
  # Create data file path
1245
  data_file = os.path.join(output_dir, "eigenvalue_data.json")
1246
 
1247
+ # Delete previous output if exists
1248
+ if os.path.exists(data_file):
1249
+ os.remove(data_file)
 
 
 
 
1250
 
1251
+ # Build command for eigenvalue analysis with the proper arguments
1252
+ cmd = [
1253
+ executable,
1254
+ "eigenvalues", # Mode argument
1255
+ str(n),
1256
+ str(p),
1257
+ str(a),
1258
+ str(y),
1259
+ str(fineness),
1260
+ str(theory_grid_points),
1261
+ str(theory_tolerance),
1262
+ data_file
1263
+ ]
1264
 
1265
+ # Run the command
1266
+ status_text.text("Running eigenvalue analysis...")
1267
 
1268
+ if debug_mode:
1269
+ success, stdout, stderr = run_command(cmd, True, timeout=timeout_seconds)
1270
+ # Process stdout for progress updates
1271
+ if success:
1272
+ progress_bar.progress(1.0)
1273
+ else:
1274
+ # Start the process with pipe for stdout to read progress
1275
+ process = subprocess.Popen(
1276
+ cmd,
1277
+ stdout=subprocess.PIPE,
1278
+ stderr=subprocess.PIPE,
1279
+ text=True,
1280
+ bufsize=1,
1281
+ universal_newlines=True
1282
+ )
1283
+
1284
+ # Track progress from stdout
1285
+ success = True
1286
+ stdout_lines = []
1287
+
1288
+ start_time = time.time()
1289
+ while True:
1290
+ # Check for timeout
1291
+ if time.time() - start_time > timeout_seconds:
1292
+ process.kill()
1293
+ status_text.error(f"Computation timed out after {timeout_seconds} seconds")
1294
+ success = False
1295
+ break
1296
+
1297
+ # Try to read a line (non-blocking)
1298
+ line = process.stdout.readline()
1299
+ if not line and process.poll() is not None:
1300
+ break
1301
+
1302
+ if line:
1303
+ stdout_lines.append(line)
1304
+ if line.startswith("PROGRESS:"):
1305
+ try:
1306
+ # Update progress bar
1307
+ progress_value = float(line.split(":")[1].strip())
1308
+ progress_bar.progress(progress_value)
1309
+ status_text.text(f"Calculating... {int(progress_value * 100)}% complete")
1310
+ except:
1311
+ pass
1312
+ elif line:
1313
+ status_text.text(line.strip())
1314
+
1315
+ # Get the return code and stderr
1316
+ returncode = process.poll()
1317
+ stderr = process.stderr.read()
1318
+
1319
+ if returncode != 0:
1320
+ success = False
1321
+ st.error(f"Error executing the analysis: {stderr}")
1322
+ with st.expander("Error Details"):
1323
+ st.code(stderr)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1324
 
1325
+ if success:
1326
+ progress_bar.progress(1.0)
1327
+ status_text.text("Analysis complete! Generating visualization...")
1328
+
1329
+ # Check if the output file was created
1330
+ if not os.path.exists(data_file):
1331
+ st.error(f"Output file not created: {data_file}")
1332
+ st.stop()
1333
+
1334
+ try:
1335
+ # Load the results from the JSON file
1336
+ with open(data_file, 'r') as f:
1337
+ data = json.load(f)
1338
+
1339
+ # Process data - convert string values to numeric
1340
+ beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
1341
+ max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
1342
+ min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
1343
+ theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
1344
+ theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
1345
+
1346
+ # Create an interactive plot using Plotly
1347
+ fig = go.Figure()
1348
+
1349
+ # Add traces for each line
1350
+ fig.add_trace(go.Scatter(
1351
+ x=beta_values,
1352
+ y=max_eigenvalues,
1353
+ mode='lines+markers',
1354
+ name='Empirical Max Eigenvalue',
1355
+ line=dict(color=color_max, width=3),
1356
+ marker=dict(
1357
+ symbol='circle',
1358
+ size=8,
1359
+ color=color_max,
1360
+ line=dict(color='white', width=1)
1361
+ ),
1362
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Max</extra>'
1363
+ ))
1364
+
1365
+ fig.add_trace(go.Scatter(
1366
+ x=beta_values,
1367
+ y=min_eigenvalues,
1368
+ mode='lines+markers',
1369
+ name='Empirical Min Eigenvalue',
1370
+ line=dict(color=color_min, width=3),
1371
+ marker=dict(
1372
+ symbol='circle',
1373
+ size=8,
1374
+ color=color_min,
1375
+ line=dict(color='white', width=1)
1376
+ ),
1377
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Empirical Min</extra>'
1378
+ ))
1379
+
1380
+ fig.add_trace(go.Scatter(
1381
+ x=beta_values,
1382
+ y=theoretical_max,
1383
+ mode='lines+markers',
1384
+ name='Theoretical Max',
1385
+ line=dict(color=color_theory_max, width=3),
1386
+ marker=dict(
1387
+ symbol='diamond',
1388
+ size=8,
1389
+ color=color_theory_max,
1390
+ line=dict(color='white', width=1)
1391
+ ),
1392
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Max</extra>'
1393
+ ))
1394
+
1395
+ fig.add_trace(go.Scatter(
1396
+ x=beta_values,
1397
+ y=theoretical_min,
1398
+ mode='lines+markers',
1399
+ name='Theoretical Min',
1400
+ line=dict(color=color_theory_min, width=3),
1401
+ marker=dict(
1402
+ symbol='diamond',
1403
+ size=8,
1404
+ color=color_theory_min,
1405
+ line=dict(color='white', width=1)
1406
+ ),
1407
+ hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
1408
+ ))
1409
+
1410
+ # Configure layout for better appearance - removed the detailed annotations
1411
+ fig.update_layout(
1412
+ title={
1413
+ 'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}',
1414
+ 'font': {'size': 24, 'color': '#0e1117'},
1415
+ 'y': 0.95,
1416
+ 'x': 0.5,
1417
+ 'xanchor': 'center',
1418
+ 'yanchor': 'top'
1419
+ },
1420
+ xaxis={
1421
+ 'title': {'text': 'β Parameter', 'font': {'size': 18, 'color': '#424242'}},
1422
+ 'tickfont': {'size': 14},
1423
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
1424
+ 'showgrid': True
1425
+ },
1426
+ yaxis={
1427
+ 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}},
1428
+ 'tickfont': {'size': 14},
1429
+ 'gridcolor': 'rgba(220, 220, 220, 0.5)',
1430
+ 'showgrid': True
1431
+ },
1432
+ plot_bgcolor='rgba(250, 250, 250, 0.8)',
1433
+ paper_bgcolor='rgba(255, 255, 255, 0.8)',
1434
+ hovermode='closest',
1435
+ legend={
1436
+ 'font': {'size': 14},
1437
+ 'bgcolor': 'rgba(255, 255, 255, 0.9)',
1438
+ 'bordercolor': 'rgba(200, 200, 200, 0.5)',
1439
+ 'borderwidth': 1
1440
+ },
1441
+ margin={'l': 60, 'r': 30, 't': 100, 'b': 60},
1442
+ height=600,
1443
+ )
1444
+
1445
+ # Add custom modebar buttons
1446
+ fig.update_layout(
1447
+ modebar_add=[
1448
+ 'drawline', 'drawopenpath', 'drawclosedpath',
1449
+ 'drawcircle', 'drawrect', 'eraseshape'
1450
+ ],
1451
+ modebar_remove=['lasso2d', 'select2d'],
1452
+ dragmode='zoom'
1453
+ )
1454
+
1455
+ # Clear progress container
1456
+ progress_container.empty()
1457
+
1458
+ # Display the interactive plot in Streamlit
1459
+ st.plotly_chart(fig, use_container_width=True)
1460
+
1461
+ # Display statistics in a cleaner way
1462
+ st.markdown('<div class="stats-box">', unsafe_allow_html=True)
1463
+ col1, col2, col3, col4 = st.columns(4)
1464
+ with col1:
1465
+ st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}")
1466
+ with col2:
1467
+ st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}")
1468
+ with col3:
1469
+ st.metric("Max Theoretical", f"{theoretical_max.max():.4f}")
1470
+ with col4:
1471
+ st.metric("Min Theoretical", f"{theoretical_min.min():.4f}")
1472
+ st.markdown('</div>', unsafe_allow_html=True)
1473
+
1474
+ except json.JSONDecodeError as e:
1475
+ st.error(f"Error parsing JSON results: {str(e)}")
1476
+ if os.path.exists(data_file):
1477
+ with open(data_file, 'r') as f:
1478
+ content = f.read()
1479
+ st.code(content[:1000] + "..." if len(content) > 1000 else content)
1480
 
1481
  except Exception as e:
1482
  st.error(f"An error occurred: {str(e)}")
1483
+ if debug_mode:
1484
+ st.exception(e)
1485
 
1486
  else:
1487
  # Try to load existing data if available
 
1498
  theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
1499
  theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
1500
 
1501
+ # Create an interactive plot using Plotly
1502
  fig = go.Figure()
1503
 
1504
  # Add traces for each line
 
1562
  hovertemplate='β: %{x:.3f}<br>Value: %{y:.6f}<extra>Theoretical Min</extra>'
1563
  ))
1564
 
1565
+ # Configure layout for better appearance
1566
  fig.update_layout(
1567
  title={
1568
  'text': f'Eigenvalue Analysis (Previous Result)',
 
1673
  # Show progress
1674
  progress_container = st.container()
1675
  with progress_container:
1676
+ progress_bar = st.progress(0)
1677
  status_text = st.empty()
1678
  status_text.text("Starting cubic equation calculations...")
1679
 
1680
  try:
1681
+ # Create data file path
1682
  data_file = os.path.join(output_dir, "cubic_data.json")
1683
 
1684
+ # Run the Im(s) vs z analysis using Python SymPy
1685
  start_time = time.time()
1686
+
1687
+ # Define progress callback for updating the progress bar
1688
+ def update_progress(progress):
1689
+ progress_bar.progress(progress)
1690
+ status_text.text(f"Calculating... {int(progress * 100)}% complete")
1691
+
1692
+ # Run the analysis with progress updates
1693
+ result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max, update_progress)
1694
  end_time = time.time()
1695
 
1696
  # Format the data for saving
 
2259
  st.markdown("""
2260
  <div class="footer">
2261
  <h3>About the Matrix Analysis Dashboard</h3>
2262
+ <p>This dashboard performs two types of analyses using different computational approaches:</p>
2263
  <ol>
2264
+ <li><strong>Eigenvalue Analysis (C++):</strong> Uses C++ with OpenCV for high-performance computation of eigenvalues of random matrices with specific structures.</li>
2265
+ <li><strong>Im(s) vs z Analysis (SymPy):</strong> Uses Python's SymPy library for symbolic mathematics to analyze the cubic equation that arises in the theoretical analysis.</li>
2266
  </ol>
2267
+ <p>This hybrid approach combines C++'s performance for data-intensive calculations with Python's expressiveness for mathematical analysis.</p>
2268
  </div>
2269
  """, unsafe_allow_html=True)