euler314 commited on
Commit
c6775fd
·
verified ·
1 Parent(s): c91a978

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +346 -157
app.py CHANGED
@@ -4,67 +4,37 @@ import numpy as np
4
  import plotly.graph_objects as go
5
  from scipy.optimize import fsolve
6
  from scipy.stats import gaussian_kde
7
- import os
8
  import sys
9
- import subprocess
10
  import importlib.util
11
 
12
- # Check if cubic_cpp is built, and build it if not
13
- def build_cpp_module():
14
- if not os.path.exists('cubic_cpp.cpp'):
15
- st.error("C++ source file not found!")
16
- return False
17
-
18
- if importlib.util.find_spec("cubic_cpp") is None:
19
- st.info("Building C++ extension module...")
20
- try:
21
- # Simple build command using pybind11
22
- cmd = [
23
- sys.executable, "-m", "pip", "install",
24
- "pybind11", "numpy", "eigen"
25
- ]
26
- subprocess.check_call(cmd)
27
-
28
- # Build the extension
29
- cmd = [
30
- sys.executable, "-m", "pip", "install",
31
- "-v", "--editable", "."
32
- ]
33
- subprocess.check_call(cmd)
34
- st.success("C++ extension module built successfully!")
35
- except subprocess.CalledProcessError as e:
36
- st.error(f"Failed to build C++ extension: {e}")
37
- return False
38
- return True
39
-
40
- # Try to import the C++ module
41
- try:
42
- import cubic_cpp
43
- cpp_available = True
44
- except ImportError:
45
- if build_cpp_module():
46
- try:
47
- import cubic_cpp
48
- cpp_available = True
49
- except ImportError:
50
- st.error("Failed to import C++ module after building.")
51
- cpp_available = False
52
- else:
53
- cpp_available = False
54
-
55
- # Configure Streamlit for Hugging Face Spaces
56
  st.set_page_config(
57
  page_title="Cubic Root Analysis",
58
  layout="wide",
59
  initial_sidebar_state="collapsed"
60
  )
61
 
 
 
 
 
 
 
 
 
62
  def add_sqrt_support(expr_str):
63
  """Replace 'sqrt(' with 'sp.sqrt(' for sympy compatibility"""
64
  return expr_str.replace('sqrt(', 'sp.sqrt(')
65
 
66
- # Define symbolic variables for the cubic discriminant using SymPy
 
 
 
 
67
  z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True, positive=True)
 
 
68
  a_sym = z_sym * z_a_sym
69
  b_sym = z_sym * z_a_sym + z_sym + z_a_sym - z_a_sym*y_sym
70
  c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
@@ -76,81 +46,335 @@ Delta_expr = (
76
  + (c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2))**3
77
  )
78
 
79
- # Use either C++ or Python implementation for numeric computations
80
- if cpp_available:
81
- # Use C++ implementations
82
- discriminant_func = cubic_cpp.discriminant_func
 
 
 
 
 
 
 
 
 
83
 
84
- @st.cache_data
85
- def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
86
- return cubic_cpp.find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- @st.cache_data
89
- def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps):
90
- return cubic_cpp.sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps)
91
 
92
- @st.cache_data
93
- def compute_eigenvalue_support_boundaries(z_a, y, beta_values, n_samples=100, seeds=5):
94
- beta_array = np.array(beta_values)
95
- return cubic_cpp.compute_eigenvalue_support_boundaries(
96
- z_a, y, beta_array, n_samples, seeds)
97
 
98
- @st.cache_data
99
- def compute_cubic_roots(z, beta, z_a, y):
100
- return cubic_cpp.compute_cubic_roots(z, beta, z_a, y)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- @st.cache_data
103
- def compute_high_y_curve(betas, z_a, y):
104
- return cubic_cpp.compute_high_y_curve(betas, z_a, y)
105
 
106
- @st.cache_data
107
- def compute_alternate_low_expr(betas, z_a, y):
108
- return cubic_cpp.compute_alternate_low_expr(betas, z_a, y)
109
 
110
- @st.cache_data
111
- def compute_max_k_expression(betas, z_a, y, k_samples=1000):
112
- return cubic_cpp.compute_max_k_expression(betas, z_a, y, k_samples)
 
 
 
 
 
 
113
 
114
- @st.cache_data
115
- def compute_min_t_expression(betas, z_a, y, t_samples=1000):
116
- return cubic_cpp.compute_min_t_expression(betas, z_a, y, t_samples)
 
 
117
 
118
- @st.cache_data
119
- def compute_derivatives(curve, betas):
120
- return cubic_cpp.compute_derivatives(curve, betas)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- @st.cache_data
123
- def generate_eigenvalue_distribution(beta, y, z_a, n, seed):
124
- return cubic_cpp.generate_eigenvalue_distribution(beta, y, z_a, n, seed)
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- else:
127
- # Original Python implementations (as fallback)
128
- # Only showing a few key functions for brevity
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- def discriminant_func(z, beta, z_a, y):
131
- """Fast numeric function for the discriminant"""
132
- # Apply the condition for y
133
- y_effective = y if y > 1 else 1/y
134
 
135
- # Coefficients
136
- a = z * z_a
137
- b = z * z_a + z + z_a - z_a*y_effective
138
- c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
139
- d = 1
 
 
 
 
 
 
 
 
140
 
141
- # Calculate the discriminant
142
- return ((b*c)/(6*a**2) - (b**3)/(27*a**3) - d/(2*a))**2 + (c/(3*a) - (b**2)/(9*a**2))**3
 
 
 
 
 
143
 
144
- # ... [rest of Python implementations]
 
 
 
 
 
145
 
146
- # The rest of the app.py remains the same as in the original file
147
- # This includes the Streamlit UI code and the functions that operate on the data
148
- # returned by the computational functions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- # ... [Original app.py from here]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- def compute_all_derivatives(betas, z_mins, z_maxs, low_y_curve, high_y_curve, alt_low_expr,
153
- custom_curve1=None, custom_curve2=None):
154
  """Compute derivatives for all curves"""
155
  derivatives = {}
156
 
@@ -382,7 +606,7 @@ def track_roots_consistently(z_values, all_roots):
382
  Ensure consistent tracking of roots across z values by minimizing discontinuity.
383
  """
384
  n_points = len(z_values)
385
- n_roots = 3 # Always 3 roots for cubic
386
  tracked_roots = np.zeros((n_points, n_roots), dtype=complex)
387
  tracked_roots[0] = all_roots[0]
388
 
@@ -435,7 +659,7 @@ def generate_cubic_discriminant(z, beta, z_a, y_effective):
435
 
436
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
437
  """
438
- Generate Im(s) and Re(s) vs. z plots with improved accuracy using C++.
439
  """
440
  if z_a <= 0 or y <= 0 or z_min >= z_max:
441
  st.error("Invalid input parameters.")
@@ -459,7 +683,7 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
459
  progress_bar.progress((i + 1) / n_points)
460
  status_text.text(f"Computing roots for z = {z:.3f} ({i+1}/{n_points})")
461
 
462
- # Calculate roots using C++ or Python
463
  roots = compute_cubic_roots(z, beta, z_a, y)
464
 
465
  # Initial sorting to help with tracking
@@ -527,7 +751,7 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
527
 
528
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
529
  """
530
- Generate Im(s) and Re(s) vs. β plots with improved accuracy using C++.
531
  """
532
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
533
  st.error("Invalid input parameters.")
@@ -551,7 +775,7 @@ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
551
  progress_bar.progress((i + 1) / n_points)
552
  status_text.text(f"Computing roots for β = {beta:.3f} ({i+1}/{n_points})")
553
 
554
- # Calculate roots using C++ or Python
555
  roots = compute_cubic_roots(z, beta, z_a, y)
556
 
557
  # Initial sorting to help with tracking
@@ -717,44 +941,12 @@ def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_ma
717
  return fig
718
 
719
  @st.cache_data
720
- def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
721
  """
722
  Generate the eigenvalue distribution of B_n = S_n T_n as n→∞
723
  """
724
- # Use C++ implementation if available
725
- if cpp_available:
726
- eigenvalues = cubic_cpp.generate_eigenvalue_distribution(beta, y, z_a, n, seed)
727
- else:
728
- # Python implementation (fallback)
729
- # Apply the condition for y
730
- y_effective = y if y > 1 else 1/y
731
-
732
- # Set random seed
733
- np.random.seed(seed)
734
-
735
- # Compute dimension p based on aspect ratio y
736
- p = int(y_effective * n)
737
-
738
- # Constructing T_n (Population / Shape Matrix)
739
- k = int(np.floor(beta * p))
740
- diag_entries = np.concatenate([
741
- np.full(k, z_a),
742
- np.full(p - k, 1.0)
743
- ])
744
- np.random.shuffle(diag_entries)
745
- T_n = np.diag(diag_entries)
746
-
747
- # Generate the data matrix X with i.i.d. standard normal entries
748
- X = np.random.randn(p, n)
749
-
750
- # Compute the sample covariance matrix S_n = (1/n) * XX^T
751
- S_n = (1 / n) * (X @ X.T)
752
-
753
- # Compute B_n = S_n T_n
754
- B_n = S_n @ T_n
755
-
756
- # Compute eigenvalues of B_n
757
- eigenvalues = np.linalg.eigvalsh(B_n)
758
 
759
  # Use KDE to compute a smooth density estimate
760
  kde = gaussian_kde(eigenvalues)
@@ -786,11 +978,8 @@ def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
786
  def main():
787
  st.title("Cubic Root Analysis")
788
 
789
- if not cpp_available:
790
- st.warning("C++ acceleration module not available. Using slower Python implementation instead.")
791
-
792
  # Define three tabs
793
- tab1, tab2, tab3, = st.tabs(["z*(β) Curves", "Complex Root Analysis", "Differential Analysis"])
794
 
795
  # ----- Tab 1: z*(β) Curves -----
796
  with tab1:
@@ -846,17 +1035,17 @@ def main():
846
  st.latex(r"\frac{y\beta(z_a-1)\underline{s}+(a\underline{s}+1)((y-1)\underline{s}-1)}{(a\underline{s}+1)(\underline{s}^2 + \underline{s})}")
847
  s_num = st.text_input("s numerator", value="", key="s_num")
848
  s_denom = st.text_input("s denominator", value="", key="s_denom")
849
-
850
  with st.expander("Custom Expression 2 (direct z(β))", expanded=False):
851
  st.markdown("""Enter direct expression for z(β) = numerator/denominator
852
  (using variables `y`, `beta`, `z_a`, and `sqrt()`)""")
853
  z_num = st.text_input("z(β) numerator", value="", key="z_num")
854
  z_denom = st.text_input("z(β) denominator", value="", key="z_denom")
855
-
856
  # Move show_derivatives to main UI level for better visibility
857
  with col2:
858
  show_derivatives = st.checkbox("Show derivatives", value=False)
859
-
860
  # Compute button
861
  if st.button("Compute Curves", key="tab1_button"):
862
  with col3:
@@ -906,7 +1095,7 @@ def main():
906
  - Dashed lines: First derivatives (d/dβ)
907
  - Dotted lines: Second derivatives (d²/dβ²)
908
  """)
909
-
910
  # ----- Tab 2: Complex Root Analysis -----
911
  with tab2:
912
  st.header("Complex Root Analysis")
@@ -947,7 +1136,7 @@ def main():
947
  These transition points align perfectly with the z*(β) boundary curves from the first tab,
948
  which represent exactly these transitions in the (β,z) plane.
949
  """)
950
-
951
  # New tab for Im{s} vs. β plot
952
  with plot_tabs[1]:
953
  col1, col2 = st.columns([1, 2])
@@ -1035,7 +1224,7 @@ def main():
1035
  which are the exact same curves as the z*(β) boundaries in the first tab. This phase
1036
  diagram provides a comprehensive view of the eigenvalue support structure.
1037
  """)
1038
-
1039
  # Eigenvalue distribution tab
1040
  with plot_tabs[3]:
1041
  st.subheader("Eigenvalue Distribution for B_n = S_n T_n")
@@ -1058,11 +1247,11 @@ def main():
1058
  # Add comparison option
1059
  show_theoretical = st.checkbox("Show theoretical boundaries", value=True)
1060
  show_empirical_stats = st.checkbox("Show empirical statistics", value=True)
1061
-
1062
  if st.button("Generate Eigenvalue Distribution", key="tab2_eigen_button"):
1063
  with col_eigen2:
1064
  # Generate the eigenvalue distribution
1065
- fig_eigen, eigenvalues = generate_eigenvalue_distribution(beta_eigen, y_eigen, z_a_eigen, n=n_samples, seed=sim_seed)
1066
 
1067
  # If requested, compute and add theoretical boundaries
1068
  if show_theoretical:
@@ -1117,7 +1306,7 @@ def main():
1117
  with col2:
1118
  st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}")
1119
  st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}")
1120
-
1121
  # ----- Tab 3: Differential Analysis -----
1122
  with tab3:
1123
  st.header("Differential Analysis vs. β")
 
4
  import plotly.graph_objects as go
5
  from scipy.optimize import fsolve
6
  from scipy.stats import gaussian_kde
 
7
  import sys
8
+ import os
9
  import importlib.util
10
 
11
+ # Configure Streamlit for Hugging Face Spaces - THIS MUST COME FIRST
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  st.set_page_config(
13
  page_title="Cubic Root Analysis",
14
  layout="wide",
15
  initial_sidebar_state="collapsed"
16
  )
17
 
18
+ # Try to import C++ module
19
+ try:
20
+ import cubic_cpp
21
+ cpp_available = True
22
+ except ImportError:
23
+ cpp_available = False
24
+ st.warning("⚠️ C++ acceleration unavailable. Using slower Python implementation.")
25
+
26
  def add_sqrt_support(expr_str):
27
  """Replace 'sqrt(' with 'sp.sqrt(' for sympy compatibility"""
28
  return expr_str.replace('sqrt(', 'sp.sqrt(')
29
 
30
+ #############################
31
+ # 1) Define the discriminant
32
+ #############################
33
+
34
+ # Symbolic variables for the cubic discriminant
35
  z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True, positive=True)
36
+
37
+ # Define coefficients a, b, c, d in terms of z_sym, beta_sym, z_a_sym, y_sym
38
  a_sym = z_sym * z_a_sym
39
  b_sym = z_sym * z_a_sym + z_sym + z_a_sym - z_a_sym*y_sym
40
  c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
 
46
  + (c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2))**3
47
  )
48
 
49
+ # Define fallback Python implementations for all functions
50
+ # These will be used if C++ module is unavailable
51
+
52
+ def discriminant_func_py(z, beta, z_a, y):
53
+ """Fast numeric function for the discriminant"""
54
+ # Apply the condition for y
55
+ y_effective = y if y > 1 else 1/y
56
+
57
+ # Coefficients
58
+ a = z * z_a
59
+ b = z * z_a + z + z_a - z_a*y_effective
60
+ c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
61
+ d = 1
62
 
63
+ # Calculate the discriminant
64
+ return ((b*c)/(6*a**2) - (b**3)/(27*a**3) - d/(2*a))**2 + (c/(3*a) - (b**2)/(9*a**2))**3
65
+
66
+ @st.cache_data
67
+ def find_z_at_discriminant_zero_py(z_a, y, beta, z_min, z_max, steps):
68
+ """
69
+ Scan z in [z_min, z_max] for sign changes in the discriminant,
70
+ and return approximated roots (where the discriminant is zero).
71
+ """
72
+ # Apply the condition for y
73
+ y_effective = y if y > 1 else 1/y
74
+
75
+ z_grid = np.linspace(z_min, z_max, steps)
76
+ disc_vals = np.array([discriminant_func_py(z, beta, z_a, y_effective) for z in z_grid])
77
+ roots_found = []
78
+ for i in range(len(z_grid) - 1):
79
+ f1, f2 = disc_vals[i], disc_vals[i+1]
80
+ if np.isnan(f1) or np.isnan(f2):
81
+ continue
82
+ if f1 == 0.0:
83
+ roots_found.append(z_grid[i])
84
+ elif f2 == 0.0:
85
+ roots_found.append(z_grid[i+1])
86
+ elif f1 * f2 < 0:
87
+ zl, zr = z_grid[i], z_grid[i+1]
88
+ for _ in range(50):
89
+ mid = 0.5 * (zl + zr)
90
+ fm = discriminant_func_py(mid, beta, z_a, y_effective)
91
+ if fm == 0:
92
+ zl = zr = mid
93
+ break
94
+ if np.sign(fm) == np.sign(f1):
95
+ zl, f1 = mid, fm
96
+ else:
97
+ zr, f2 = mid, fm
98
+ roots_found.append(0.5 * (zl + zr))
99
+ return np.array(roots_found)
100
+
101
+ @st.cache_data
102
+ def sweep_beta_and_find_z_bounds_py(z_a, y, z_min, z_max, beta_steps, z_steps):
103
+ """
104
+ For each beta in [0,1] (with beta_steps points), find the minimum and maximum z
105
+ for which the discriminant is zero.
106
+ Returns: betas, lower z*(β) values, and upper z*(β) values.
107
+ """
108
+ betas = np.linspace(0, 1, beta_steps)
109
+ z_min_values = []
110
+ z_max_values = []
111
+ for b in betas:
112
+ roots = find_z_at_discriminant_zero_py(z_a, y, b, z_min, z_max, z_steps)
113
+ if len(roots) == 0:
114
+ z_min_values.append(np.nan)
115
+ z_max_values.append(np.nan)
116
+ else:
117
+ z_min_values.append(np.min(roots))
118
+ z_max_values.append(np.max(roots))
119
+ return betas, np.array(z_min_values), np.array(z_max_values)
120
+
121
+ @st.cache_data
122
+ def compute_eigenvalue_support_boundaries_py(z_a, y, beta_values, n_samples=100, seeds=5):
123
+ """
124
+ Compute the support boundaries of the eigenvalue distribution by directly
125
+ finding the minimum and maximum eigenvalues of B_n = S_n T_n for different beta values.
126
+ """
127
+ # Apply the condition for y
128
+ y_effective = y if y > 1 else 1/y
129
 
130
+ min_eigenvalues = np.zeros_like(beta_values)
131
+ max_eigenvalues = np.zeros_like(beta_values)
 
132
 
133
+ # Use a progress bar for Streamlit
134
+ progress_bar = st.progress(0)
135
+ status_text = st.empty()
 
 
136
 
137
+ for i, beta in enumerate(beta_values):
138
+ # Update progress
139
+ progress_bar.progress((i + 1) / len(beta_values))
140
+ status_text.text(f"Processing β = {beta:.2f} ({i+1}/{len(beta_values)})")
141
+
142
+ min_vals = []
143
+ max_vals = []
144
+
145
+ # Run multiple trials with different seeds for more stable results
146
+ for seed in range(seeds):
147
+ # Set random seed
148
+ np.random.seed(seed * 100 + i)
149
+
150
+ # Compute dimension p based on aspect ratio y
151
+ n = n_samples
152
+ p = int(y_effective * n)
153
+
154
+ # Constructing T_n (Population / Shape Matrix)
155
+ k = int(np.floor(beta * p))
156
+ diag_entries = np.concatenate([
157
+ np.full(k, z_a),
158
+ np.full(p - k, 1.0)
159
+ ])
160
+ np.random.shuffle(diag_entries)
161
+ T_n = np.diag(diag_entries)
162
+
163
+ # Generate the data matrix X with i.i.d. standard normal entries
164
+ X = np.random.randn(p, n)
165
+
166
+ # Compute the sample covariance matrix S_n = (1/n) * XX^T
167
+ S_n = (1 / n) * (X @ X.T)
168
+
169
+ # Compute B_n = S_n T_n
170
+ B_n = S_n @ T_n
171
+
172
+ # Compute eigenvalues of B_n
173
+ eigenvalues = np.linalg.eigvalsh(B_n)
174
+
175
+ # Find minimum and maximum eigenvalues
176
+ min_vals.append(np.min(eigenvalues))
177
+ max_vals.append(np.max(eigenvalues))
178
 
179
+ # Average over seeds for stability
180
+ min_eigenvalues[i] = np.mean(min_vals)
181
+ max_eigenvalues[i] = np.mean(max_vals)
182
 
183
+ # Clear progress indicators
184
+ progress_bar.empty()
185
+ status_text.empty()
186
 
187
+ return min_eigenvalues, max_eigenvalues
188
+
189
+ @st.cache_data
190
+ def compute_cubic_roots_py(z, beta, z_a, y):
191
+ """
192
+ Compute the roots of the cubic equation for given parameters.
193
+ """
194
+ # Apply the condition for y
195
+ y_effective = y if y > 1 else 1/y
196
 
197
+ # Coefficients in the form as^3 + bs^2 + cs + d = 0
198
+ a = z * z_a
199
+ b = z * z_a + z + z_a - z_a*y_effective
200
+ c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
201
+ d = 1
202
 
203
+ # Handle special cases
204
+ if abs(a) < 1e-10:
205
+ if abs(b) < 1e-10: # Linear case
206
+ roots = np.array([-d/c, 0, 0], dtype=complex)
207
+ else: # Quadratic case
208
+ quad_roots = np.roots([b, c, d])
209
+ roots = np.append(quad_roots, 0).astype(complex)
210
+ return roots
211
+
212
+ # Standard cubic case
213
+ coeffs = [a, b, c, d]
214
+ return np.roots(coeffs)
215
+
216
+ @st.cache_data
217
+ def compute_high_y_curve_py(betas, z_a, y):
218
+ """
219
+ Compute the "High y Expression" curve.
220
+ """
221
+ # Apply the condition for y
222
+ y_effective = y if y > 1 else 1/y
223
+
224
+ a = z_a
225
+ betas = np.array(betas)
226
+ denominator = 1 - 2*a
227
+ if denominator == 0:
228
+ return np.full_like(betas, np.nan)
229
+ numerator = -4*a*(a-1)*y_effective*betas - 2*a*y_effective - 2*a*(2*a-1)
230
+ return numerator/denominator
231
+
232
+ @st.cache_data
233
+ def compute_alternate_low_expr_py(betas, z_a, y):
234
+ """
235
+ Compute the alternate low expression.
236
+ """
237
+ # Apply the condition for y
238
+ y_effective = y if y > 1 else 1/y
239
+
240
+ betas = np.array(betas)
241
+ return (z_a * y_effective * betas * (z_a - 1) - 2*z_a*(1 - y_effective) - 2*z_a**2) / (2 + 2*z_a)
242
+
243
+ @st.cache_data
244
+ def compute_max_k_expression_py(betas, z_a, y, k_samples=1000):
245
+ """
246
+ Compute max_{k ∈ (0,∞)} (y*beta*(a-1)*k + (a*k+1)*((y-1)*k-1)) / ((a*k+1)*(k^2+k))
247
+ """
248
+ # Apply the condition for y
249
+ y_effective = y if y > 1 else 1/y
250
 
251
+ a = z_a
252
+ # Sample k values on a logarithmic scale
253
+ k_values = np.logspace(-3, 3, k_samples)
254
+
255
+ max_vals = np.zeros_like(betas)
256
+ for i, beta in enumerate(betas):
257
+ values = np.zeros_like(k_values)
258
+ for j, k in enumerate(k_values):
259
+ numerator = y_effective*beta*(a-1)*k + (a*k+1)*((y_effective-1)*k-1)
260
+ denominator = (a*k+1)*(k**2+k)
261
+ if abs(denominator) < 1e-10:
262
+ values[j] = np.nan
263
+ else:
264
+ values[j] = numerator/denominator
265
 
266
+ valid_indices = ~np.isnan(values)
267
+ if np.any(valid_indices):
268
+ max_vals[i] = np.max(values[valid_indices])
269
+ else:
270
+ max_vals[i] = np.nan
271
+
272
+ return max_vals
273
+
274
+ @st.cache_data
275
+ def compute_min_t_expression_py(betas, z_a, y, t_samples=1000):
276
+ """
277
+ Compute min_{t ∈ (-1/a, 0)} (y*beta*(a-1)*t + (a*t+1)*((y-1)*t-1)) / ((a*t+1)*(t^2+t))
278
+ """
279
+ # Apply the condition for y
280
+ y_effective = y if y > 1 else 1/y
281
 
282
+ a = z_a
283
+ if a <= 0:
284
+ return np.full_like(betas, np.nan)
 
285
 
286
+ lower_bound = -1/a + 1e-10 # Avoid division by zero
287
+ t_values = np.linspace(lower_bound, -1e-10, t_samples)
288
+
289
+ min_vals = np.zeros_like(betas)
290
+ for i, beta in enumerate(betas):
291
+ values = np.zeros_like(t_values)
292
+ for j, t in enumerate(t_values):
293
+ numerator = y_effective*beta*(a-1)*t + (a*t+1)*((y_effective-1)*t-1)
294
+ denominator = (a*t+1)*(t**2+t)
295
+ if abs(denominator) < 1e-10:
296
+ values[j] = np.nan
297
+ else:
298
+ values[j] = numerator/denominator
299
 
300
+ valid_indices = ~np.isnan(values)
301
+ if np.any(valid_indices):
302
+ min_vals[i] = np.min(values[valid_indices])
303
+ else:
304
+ min_vals[i] = np.nan
305
+
306
+ return min_vals
307
 
308
+ @st.cache_data
309
+ def compute_derivatives_py(curve, betas):
310
+ """Compute first and second derivatives of a curve"""
311
+ d1 = np.gradient(curve, betas)
312
+ d2 = np.gradient(d1, betas)
313
+ return d1, d2
314
 
315
+ @st.cache_data
316
+ def generate_eigenvalue_distribution_py(beta, y, z_a, n=1000, seed=42):
317
+ """
318
+ Generate the eigenvalue distribution of B_n = S_n T_n as n→∞
319
+ """
320
+ # Apply the condition for y
321
+ y_effective = y if y > 1 else 1/y
322
+
323
+ # Set random seed
324
+ np.random.seed(seed)
325
+
326
+ # Compute dimension p based on aspect ratio y
327
+ p = int(y_effective * n)
328
+
329
+ # Constructing T_n (Population / Shape Matrix)
330
+ k = int(np.floor(beta * p))
331
+ diag_entries = np.concatenate([
332
+ np.full(k, z_a),
333
+ np.full(p - k, 1.0)
334
+ ])
335
+ np.random.shuffle(diag_entries)
336
+ T_n = np.diag(diag_entries)
337
+
338
+ # Generate the data matrix X with i.i.d. standard normal entries
339
+ X = np.random.randn(p, n)
340
+
341
+ # Compute the sample covariance matrix S_n = (1/n) * XX^T
342
+ S_n = (1 / n) * (X @ X.T)
343
+
344
+ # Compute B_n = S_n T_n
345
+ B_n = S_n @ T_n
346
+
347
+ # Compute eigenvalues of B_n
348
+ eigenvalues = np.linalg.eigvalsh(B_n)
349
+ return eigenvalues
350
 
351
+ # Use C++ implementations if available, otherwise use Python implementations
352
+ if cpp_available:
353
+ discriminant_func = cubic_cpp.discriminant_func
354
+ find_z_at_discriminant_zero = cubic_cpp.find_z_at_discriminant_zero
355
+ sweep_beta_and_find_z_bounds = cubic_cpp.sweep_beta_and_find_z_bounds
356
+ compute_eigenvalue_support_boundaries = cubic_cpp.compute_eigenvalue_support_boundaries
357
+ compute_cubic_roots = cubic_cpp.compute_cubic_roots
358
+ compute_high_y_curve = cubic_cpp.compute_high_y_curve
359
+ compute_alternate_low_expr = cubic_cpp.compute_alternate_low_expr
360
+ compute_max_k_expression = cubic_cpp.compute_max_k_expression
361
+ compute_min_t_expression = cubic_cpp.compute_min_t_expression
362
+ compute_derivatives = cubic_cpp.compute_derivatives
363
+ generate_eigenvalue_distribution = lambda beta, y, z_a, n=1000, seed=42: cubic_cpp.generate_eigenvalue_distribution(beta, y, z_a, n, seed)
364
+ else:
365
+ discriminant_func = discriminant_func_py
366
+ find_z_at_discriminant_zero = find_z_at_discriminant_zero_py
367
+ sweep_beta_and_find_z_bounds = sweep_beta_and_find_z_bounds_py
368
+ compute_eigenvalue_support_boundaries = compute_eigenvalue_support_boundaries_py
369
+ compute_cubic_roots = compute_cubic_roots_py
370
+ compute_high_y_curve = compute_high_y_curve_py
371
+ compute_alternate_low_expr = compute_alternate_low_expr_py
372
+ compute_max_k_expression = compute_max_k_expression_py
373
+ compute_min_t_expression = compute_min_t_expression_py
374
+ compute_derivatives = compute_derivatives_py
375
+ generate_eigenvalue_distribution = generate_eigenvalue_distribution_py
376
 
377
+ def compute_all_derivatives(betas, z_mins, z_maxs, low_y_curve, high_y_curve, alt_low_expr, custom_curve1=None, custom_curve2=None):
 
378
  """Compute derivatives for all curves"""
379
  derivatives = {}
380
 
 
606
  Ensure consistent tracking of roots across z values by minimizing discontinuity.
607
  """
608
  n_points = len(z_values)
609
+ n_roots = len(all_roots[0])
610
  tracked_roots = np.zeros((n_points, n_roots), dtype=complex)
611
  tracked_roots[0] = all_roots[0]
612
 
 
659
 
660
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
661
  """
662
+ Generate Im(s) and Re(s) vs. z plots with improved accuracy using SymPy.
663
  """
664
  if z_a <= 0 or y <= 0 or z_min >= z_max:
665
  st.error("Invalid input parameters.")
 
683
  progress_bar.progress((i + 1) / n_points)
684
  status_text.text(f"Computing roots for z = {z:.3f} ({i+1}/{n_points})")
685
 
686
+ # Calculate roots
687
  roots = compute_cubic_roots(z, beta, z_a, y)
688
 
689
  # Initial sorting to help with tracking
 
751
 
752
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
753
  """
754
+ Generate Im(s) and Re(s) vs. β plots with improved accuracy.
755
  """
756
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
757
  st.error("Invalid input parameters.")
 
775
  progress_bar.progress((i + 1) / n_points)
776
  status_text.text(f"Computing roots for β = {beta:.3f} ({i+1}/{n_points})")
777
 
778
+ # Calculate roots
779
  roots = compute_cubic_roots(z, beta, z_a, y)
780
 
781
  # Initial sorting to help with tracking
 
941
  return fig
942
 
943
  @st.cache_data
944
+ def generate_eigenvalue_distribution_plot(beta, y, z_a, n=1000, seed=42):
945
  """
946
  Generate the eigenvalue distribution of B_n = S_n T_n as n→∞
947
  """
948
+ # Generate eigenvalues
949
+ eigenvalues = generate_eigenvalue_distribution(beta, y, z_a, n, seed)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
951
  # Use KDE to compute a smooth density estimate
952
  kde = gaussian_kde(eigenvalues)
 
978
  def main():
979
  st.title("Cubic Root Analysis")
980
 
 
 
 
981
  # Define three tabs
982
+ tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Complex Root Analysis", "Differential Analysis"])
983
 
984
  # ----- Tab 1: z*(β) Curves -----
985
  with tab1:
 
1035
  st.latex(r"\frac{y\beta(z_a-1)\underline{s}+(a\underline{s}+1)((y-1)\underline{s}-1)}{(a\underline{s}+1)(\underline{s}^2 + \underline{s})}")
1036
  s_num = st.text_input("s numerator", value="", key="s_num")
1037
  s_denom = st.text_input("s denominator", value="", key="s_denom")
1038
+
1039
  with st.expander("Custom Expression 2 (direct z(β))", expanded=False):
1040
  st.markdown("""Enter direct expression for z(β) = numerator/denominator
1041
  (using variables `y`, `beta`, `z_a`, and `sqrt()`)""")
1042
  z_num = st.text_input("z(β) numerator", value="", key="z_num")
1043
  z_denom = st.text_input("z(β) denominator", value="", key="z_denom")
1044
+
1045
  # Move show_derivatives to main UI level for better visibility
1046
  with col2:
1047
  show_derivatives = st.checkbox("Show derivatives", value=False)
1048
+
1049
  # Compute button
1050
  if st.button("Compute Curves", key="tab1_button"):
1051
  with col3:
 
1095
  - Dashed lines: First derivatives (d/dβ)
1096
  - Dotted lines: Second derivatives (d²/dβ²)
1097
  """)
1098
+
1099
  # ----- Tab 2: Complex Root Analysis -----
1100
  with tab2:
1101
  st.header("Complex Root Analysis")
 
1136
  These transition points align perfectly with the z*(β) boundary curves from the first tab,
1137
  which represent exactly these transitions in the (β,z) plane.
1138
  """)
1139
+
1140
  # New tab for Im{s} vs. β plot
1141
  with plot_tabs[1]:
1142
  col1, col2 = st.columns([1, 2])
 
1224
  which are the exact same curves as the z*(β) boundaries in the first tab. This phase
1225
  diagram provides a comprehensive view of the eigenvalue support structure.
1226
  """)
1227
+
1228
  # Eigenvalue distribution tab
1229
  with plot_tabs[3]:
1230
  st.subheader("Eigenvalue Distribution for B_n = S_n T_n")
 
1247
  # Add comparison option
1248
  show_theoretical = st.checkbox("Show theoretical boundaries", value=True)
1249
  show_empirical_stats = st.checkbox("Show empirical statistics", value=True)
1250
+
1251
  if st.button("Generate Eigenvalue Distribution", key="tab2_eigen_button"):
1252
  with col_eigen2:
1253
  # Generate the eigenvalue distribution
1254
+ fig_eigen, eigenvalues = generate_eigenvalue_distribution_plot(beta_eigen, y_eigen, z_a_eigen, n=n_samples, seed=sim_seed)
1255
 
1256
  # If requested, compute and add theoretical boundaries
1257
  if show_theoretical:
 
1306
  with col2:
1307
  st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}")
1308
  st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}")
1309
+
1310
  # ----- Tab 3: Differential Analysis -----
1311
  with tab3:
1312
  st.header("Differential Analysis vs. β")