euler314 commited on
Commit
9aa7702
·
verified ·
1 Parent(s): 92b8ec4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +477 -145
app.py CHANGED
@@ -9,6 +9,8 @@ import tempfile
9
  import subprocess
10
  import sys
11
  import importlib.util
 
 
12
 
13
  # Configure Streamlit for Hugging Face Spaces
14
  st.set_page_config(
@@ -17,10 +19,99 @@ st.set_page_config(
17
  initial_sidebar_state="collapsed"
18
  )
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # Check if C++ module is already compiled, otherwise compile it
21
  cpp_compiled = False
22
 
23
  def compile_cpp_module():
 
 
24
  # Define C++ code as a string
25
  cpp_code = """
26
  #include <pybind11/pybind11.h>
@@ -33,6 +124,7 @@ def compile_cpp_module():
33
  #include <random>
34
  #include <cmath>
35
  #include <algorithm>
 
36
 
37
  namespace py = pybind11;
38
  using namespace Eigen;
@@ -57,6 +149,7 @@ def compile_cpp_module():
57
  double* z_ptr = static_cast<double*>(z_buf.ptr);
58
  double* result_ptr = static_cast<double*>(result_buf.ptr);
59
 
 
60
  for (size_t i = 0; i < z_buf.size; i++) {
61
  result_ptr[i] = compute_discriminant_fast(z_ptr[i], beta, z_a, y);
62
  }
@@ -642,13 +735,17 @@ def compile_cpp_module():
642
  }
643
  """
644
 
 
 
645
  # Create a temporary directory to compile the C++ code
646
  with tempfile.TemporaryDirectory() as tmpdirname:
647
  # Write C++ code to file
 
648
  with open(os.path.join(tmpdirname, "cubic_cpp.cpp"), "w") as f:
649
  f.write(cpp_code)
650
 
651
  # Write setup.py for compiling with pybind11
 
652
  setup_py = """
653
  from setuptools import setup, Extension
654
  from pybind11.setup_helpers import Pybind11Extension, build_ext
@@ -675,7 +772,7 @@ setup(
675
  f.write(setup_py)
676
 
677
  # Compile the module
678
- st.info("Compiling C++ module... This may take a moment.")
679
  try:
680
  result = subprocess.run(
681
  [sys.executable, "setup.py", "build_ext", "--inplace"],
@@ -693,12 +790,14 @@ setup(
693
  break
694
 
695
  if not module_path:
 
696
  st.error("Failed to find compiled module.")
697
  st.code(result.stdout)
698
  st.code(result.stderr)
699
  return False
700
 
701
  # Import the module
 
702
  spec = importlib.util.spec_from_file_location("cubic_cpp", module_path)
703
  cubic_cpp = importlib.util.module_from_spec(spec)
704
  spec.loader.exec_module(cubic_cpp)
@@ -715,10 +814,11 @@ setup(
715
  globals()["generate_eigenvalue_distribution_cpp"] = cubic_cpp.generate_eigenvalue_distribution_cpp
716
  globals()["generate_phase_diagram_cpp"] = cubic_cpp.generate_phase_diagram_cpp
717
 
718
- st.success("C++ module compiled successfully!")
719
  return True
720
 
721
  except subprocess.CalledProcessError as e:
 
722
  st.error(f"Compilation failed: {e}")
723
  st.code(e.stdout)
724
  st.code(e.stderr)
@@ -738,10 +838,13 @@ def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
738
  Scan z in [z_min, z_max] for sign changes in the discriminant,
739
  and return approximated roots (where the discriminant is zero).
740
  """
 
 
 
741
  # Apply the condition for y
742
  y_effective = y if y > 1 else 1/y
743
 
744
- # Symbolic variables for the cubic discriminant
745
  z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True, positive=True)
746
 
747
  # Define coefficients a, b, c, d in terms of z_sym, beta_sym, z_a_sym, y_sym
@@ -750,39 +853,54 @@ def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
750
  c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
751
  d_sym = 1
752
 
753
- # Symbolic expression for the cubic discriminant
754
- Delta_expr = (
755
- ((b_sym*c_sym)/(6*a_sym**2) - (b_sym**3)/(27*a_sym**3) - d_sym/(2*a_sym))**2
756
- + (c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2))**3
757
- )
758
 
759
  # Fast numeric function for the discriminant
760
- discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "numpy")
761
 
762
  z_grid = np.linspace(z_min, z_max, steps)
763
- disc_vals = discriminant_func(z_grid, beta, z_a, y_effective)
 
 
 
 
 
 
 
764
  roots_found = []
 
 
 
765
  for i in range(len(z_grid) - 1):
766
  f1, f2 = disc_vals[i], disc_vals[i+1]
767
  if np.isnan(f1) or np.isnan(f2):
768
  continue
769
- if f1 == 0.0:
770
  roots_found.append(z_grid[i])
771
- elif f2 == 0.0:
772
  roots_found.append(z_grid[i+1])
773
  elif f1 * f2 < 0:
 
774
  zl, zr = z_grid[i], z_grid[i+1]
 
 
 
775
  for _ in range(50):
776
- mid = 0.5 * (zl + zr)
777
  fm = discriminant_func(mid, beta, z_a, y_effective)
778
- if fm == 0:
779
  zl = zr = mid
780
  break
781
- if np.sign(fm) == np.sign(f1):
782
  zl, f1 = mid, fm
783
  else:
784
  zr, f2 = mid, fm
785
- roots_found.append(0.5 * (zl + zr))
 
 
 
 
786
  return np.array(roots_found)
787
 
788
  @st.cache_data
@@ -795,10 +913,15 @@ def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps):
795
  if cpp_compiled:
796
  return find_discriminant_zeros(z_a, y, z_min, z_max, beta_steps, z_steps)
797
 
 
 
 
798
  betas = np.linspace(0, 1, beta_steps)
799
  z_min_values = []
800
  z_max_values = []
801
- for b in betas:
 
 
802
  roots = find_z_at_discriminant_zero(z_a, y, b, z_min, z_max, z_steps)
803
  if len(roots) == 0:
804
  z_min_values.append(np.nan)
@@ -806,6 +929,8 @@ def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps):
806
  else:
807
  z_min_values.append(np.min(roots))
808
  z_max_values.append(np.max(roots))
 
 
809
  return betas, np.array(z_min_values), np.array(z_max_values)
810
 
811
  @st.cache_data
@@ -817,20 +942,18 @@ def compute_eigenvalue_support_boundaries(z_a, y, beta_values, n_samples=100, se
817
  if cpp_compiled:
818
  return compute_eigenvalue_boundaries(z_a, y, beta_values, n_samples, seeds)
819
 
 
 
 
820
  # Apply the condition for y
821
  y_effective = y if y > 1 else 1/y
822
 
823
  min_eigenvalues = np.zeros_like(beta_values)
824
  max_eigenvalues = np.zeros_like(beta_values)
825
 
826
- # Use a progress bar for Streamlit
827
- progress_bar = st.progress(0)
828
- status_text = st.empty()
829
-
830
  for i, beta in enumerate(beta_values):
831
  # Update progress
832
- progress_bar.progress((i + 1) / len(beta_values))
833
- status_text.text(f"Processing β = {beta:.2f} ({i+1}/{len(beta_values)})")
834
 
835
  min_vals = []
836
  max_vals = []
@@ -873,92 +996,156 @@ def compute_eigenvalue_support_boundaries(z_a, y, beta_values, n_samples=100, se
873
  min_eigenvalues[i] = np.mean(min_vals)
874
  max_eigenvalues[i] = np.mean(max_vals)
875
 
876
- # Clear progress indicators
877
- progress_bar.empty()
878
- status_text.empty()
879
-
880
  return min_eigenvalues, max_eigenvalues
881
 
882
  @st.cache_data
883
  def compute_high_y_curve(betas, z_a, y):
884
  """
885
- Compute the "High y Expression" curve.
886
  """
887
  if cpp_compiled:
888
  return compute_high_y_curve(betas, z_a, y)
889
 
 
 
 
890
  # Apply the condition for y
891
  y_effective = y if y > 1 else 1/y
892
 
893
- a = z_a
894
- betas = np.array(betas)
 
895
  denominator = 1 - 2*a
896
- if denominator == 0:
897
- return np.full_like(betas, np.nan)
898
- numerator = -4*a*(a-1)*y_effective*betas - 2*a*y_effective - 2*a*(2*a-1)
899
- return numerator/denominator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
 
901
  @st.cache_data
902
  def compute_alternate_low_expr(betas, z_a, y):
903
  """
904
- Compute the alternate low expression.
905
  """
906
  if cpp_compiled:
907
  return compute_alternate_low_expr(betas, z_a, y)
908
 
 
 
 
909
  # Apply the condition for y
910
  y_effective = y if y > 1 else 1/y
911
 
912
- betas = np.array(betas)
913
- return (z_a * y_effective * betas * (z_a - 1) - 2*z_a*(1 - y_effective) - 2*z_a**2) / (2 + 2*z_a)
 
 
 
 
 
 
 
 
 
 
 
 
 
914
 
915
  @st.cache_data
916
  def compute_max_k_expression(betas, z_a, y, k_samples=1000):
917
  """
918
  Compute max_{k ∈ (0,∞)} (y*beta*(a-1)*k + (a*k+1)*((y-1)*k-1)) / ((a*k+1)*(k^2+k))
 
919
  """
920
  if cpp_compiled:
921
  return compute_max_k_expression(betas, z_a, y, k_samples)
922
 
 
 
 
923
  # Apply the condition for y
924
  y_effective = y if y > 1 else 1/y
925
 
926
- a = z_a
 
 
 
 
 
 
 
 
 
927
  # Sample k values on a logarithmic scale
928
  k_values = np.logspace(-3, 3, k_samples)
929
 
930
  max_vals = np.zeros_like(betas)
931
  for i, beta in enumerate(betas):
 
 
932
  values = np.zeros_like(k_values)
933
  for j, k in enumerate(k_values):
934
- numerator = y_effective*beta*(a-1)*k + (a*k+1)*((y_effective-1)*k-1)
935
- denominator = (a*k+1)*(k**2+k)
936
- if abs(denominator) < 1e-10:
 
 
 
 
937
  values[j] = np.nan
938
- else:
939
- values[j] = numerator/denominator
940
 
941
  valid_indices = ~np.isnan(values)
942
  if np.any(valid_indices):
943
  max_vals[i] = np.max(values[valid_indices])
944
  else:
945
  max_vals[i] = np.nan
946
-
 
947
  return max_vals
948
 
949
  @st.cache_data
950
  def compute_min_t_expression(betas, z_a, y, t_samples=1000):
951
  """
952
  Compute min_{t ∈ (-1/a, 0)} (y*beta*(a-1)*t + (a*t+1)*((y-1)*t-1)) / ((a*t+1)*(t^2+t))
 
953
  """
954
  if cpp_compiled:
955
  return compute_min_t_expression(betas, z_a, y, t_samples)
956
 
 
 
 
957
  # Apply the condition for y
958
  y_effective = y if y > 1 else 1/y
959
 
 
 
 
 
 
 
 
 
 
 
960
  a = z_a
961
  if a <= 0:
 
962
  return np.full_like(betas, np.nan)
963
 
964
  lower_bound = -1/a + 1e-10 # Avoid division by zero
@@ -966,75 +1153,134 @@ def compute_min_t_expression(betas, z_a, y, t_samples=1000):
966
 
967
  min_vals = np.zeros_like(betas)
968
  for i, beta in enumerate(betas):
 
 
969
  values = np.zeros_like(t_values)
970
  for j, t in enumerate(t_values):
971
- numerator = y_effective*beta*(a-1)*t + (a*t+1)*((y_effective-1)*t-1)
972
- denominator = (a*t+1)*(t**2+t)
973
- if abs(denominator) < 1e-10:
 
 
 
 
974
  values[j] = np.nan
975
- else:
976
- values[j] = numerator/denominator
977
 
978
  valid_indices = ~np.isnan(values)
979
  if np.any(valid_indices):
980
  min_vals[i] = np.min(values[valid_indices])
981
  else:
982
  min_vals[i] = np.nan
983
-
 
984
  return min_vals
985
 
986
  @st.cache_data
987
  def compute_derivatives(curve, betas):
988
- """Compute first and second derivatives of a curve"""
989
- d1 = np.gradient(curve, betas)
990
- d2 = np.gradient(d1, betas)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
991
  return d1, d2
992
 
993
  def compute_all_derivatives(betas, z_mins, z_maxs, low_y_curve, high_y_curve, alt_low_expr, custom_curve1=None, custom_curve2=None):
994
  """Compute derivatives for all curves"""
 
 
995
  derivatives = {}
996
 
997
  # Upper z*(β)
 
998
  derivatives['upper'] = compute_derivatives(z_maxs, betas)
999
 
1000
  # Lower z*(β)
 
1001
  derivatives['lower'] = compute_derivatives(z_mins, betas)
1002
 
1003
  # Low y Expression (only if provided)
1004
  if low_y_curve is not None:
 
1005
  derivatives['low_y'] = compute_derivatives(low_y_curve, betas)
 
 
1006
 
1007
  # High y Expression
1008
  if high_y_curve is not None:
 
1009
  derivatives['high_y'] = compute_derivatives(high_y_curve, betas)
 
 
1010
 
1011
  # Alternate Low Expression
1012
  if alt_low_expr is not None:
 
1013
  derivatives['alt_low'] = compute_derivatives(alt_low_expr, betas)
 
 
1014
 
1015
  # Custom Expression 1 (if provided)
1016
  if custom_curve1 is not None:
 
1017
  derivatives['custom1'] = compute_derivatives(custom_curve1, betas)
 
 
1018
 
1019
  # Custom Expression 2 (if provided)
1020
  if custom_curve2 is not None:
 
1021
  derivatives['custom2'] = compute_derivatives(custom_curve2, betas)
1022
-
 
 
 
1023
  return derivatives
1024
 
1025
  def compute_custom_expression(betas, z_a, y, s_num_expr, s_denom_expr, is_s_based=True):
1026
  """
1027
- Compute custom curve. If is_s_based=True, compute using s substitution.
1028
- Otherwise, compute direct z(β) expression.
1029
  """
 
 
1030
  # Apply the condition for y
1031
  y_effective = y if y > 1 else 1/y
1032
 
 
1033
  beta_sym, z_a_sym, y_sym = sp.symbols("beta z_a y", positive=True)
1034
  local_dict = {"beta": beta_sym, "z_a": z_a_sym, "y": y_sym, "sp": sp}
1035
 
1036
  try:
1037
  # Add sqrt support
 
1038
  s_num_expr = add_sqrt_support(s_num_expr)
1039
  s_denom_expr = add_sqrt_support(s_denom_expr)
1040
 
@@ -1043,6 +1289,7 @@ def compute_custom_expression(betas, z_a, y, s_num_expr, s_denom_expr, is_s_base
1043
 
1044
  if is_s_based:
1045
  # Compute s and substitute into main expression
 
1046
  s_expr = num_expr / denom_expr
1047
  a = z_a_sym
1048
  numerator = y_sym*beta_sym*(z_a_sym-1)*s_expr + (a*s_expr+1)*((y_sym-1)*s_expr-1)
@@ -1050,22 +1297,39 @@ def compute_custom_expression(betas, z_a, y, s_num_expr, s_denom_expr, is_s_base
1050
  final_expr = numerator/denominator
1051
  else:
1052
  # Direct z(β) expression
 
1053
  final_expr = num_expr / denom_expr
1054
 
1055
  except sp.SympifyError as e:
 
1056
  st.error(f"Error parsing expressions: {e}")
1057
  return np.full_like(betas, np.nan)
1058
 
1059
- final_func = sp.lambdify((beta_sym, z_a_sym, y_sym), final_expr, modules=["numpy"])
1060
- with np.errstate(divide='ignore', invalid='ignore'):
1061
- result = final_func(betas, z_a, y_effective)
1062
- if np.isscalar(result):
1063
- result = np.full_like(betas, result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  return result
1065
 
1066
  def compute_cubic_roots(z, beta, z_a, y):
1067
  """
1068
- Compute the roots of the cubic equation for given parameters, using C++ if available.
1069
  """
1070
  if cpp_compiled:
1071
  roots = compute_cubic_roots_cpp(z, beta, z_a, y)
@@ -1074,11 +1338,8 @@ def compute_cubic_roots(z, beta, z_a, y):
1074
  # Apply the condition for y
1075
  y_effective = y if y > 1 else 1/y
1076
 
1077
- # Import SymPy functions
1078
- from sympy import symbols, solve, im, re, N, Poly
1079
-
1080
  # Create a symbolic variable for the equation
1081
- s = symbols('s')
1082
 
1083
  # Coefficients in the form as^3 + bs^2 + cs + d = 0
1084
  a = z * z_a
@@ -1087,26 +1348,31 @@ def compute_cubic_roots(z, beta, z_a, y):
1087
  d = 1
1088
 
1089
  # Handle special cases
1090
- if abs(a) < 1e-10:
1091
- if abs(b) < 1e-10: # Linear case
1092
  roots = np.array([-d/c, 0, 0], dtype=complex)
1093
  else: # Quadratic case
1094
- quad_roots = np.roots([b, c, d])
1095
- roots = np.append(quad_roots, 0).astype(complex)
 
 
 
 
1096
  return roots
1097
 
1098
  try:
1099
- # Create the cubic polynomial
1100
- cubic_eq = Poly(a*s**3 + b*s**2 + c*s + d, s)
1101
 
1102
- # Solve the equation symbolically
1103
- symbolic_roots = solve(cubic_eq, s)
1104
 
1105
- # Convert symbolic roots to complex numbers with high precision
1106
  numerical_roots = []
1107
  for root in symbolic_roots:
1108
- # Use SymPy's N function with high precision
1109
- numerical_root = complex(N(root, 30))
 
1110
  numerical_roots.append(numerical_root)
1111
 
1112
  # If we got fewer than 3 roots (due to multiplicity), pad with zeros
@@ -1163,22 +1429,40 @@ def track_roots_consistently(z_values, all_roots):
1163
 
1164
  def generate_cubic_discriminant(z, beta, z_a, y_effective):
1165
  """
1166
- Calculate the cubic discriminant using the standard formula.
1167
- For a cubic ax^3 + bx^2 + cx + d:
1168
- Δ = 18abcd - 27a^2d^2 + b^2c^2 - 2b^3d - 9ac^3
1169
  """
1170
- a = z * z_a
1171
- b = z * z_a + z + z_a - z_a*y_effective
1172
- c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
1173
- d = 1
 
 
 
 
1174
 
1175
  # Standard formula for cubic discriminant
1176
- discriminant = (18*a*b*c*d - 27*a**2*d**2 + b**2*c**2 - 2*b**3*d - 9*a*c**3)
1177
- return discriminant
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
 
1179
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1180
  """
1181
- Generate Im(s) and Re(s) vs. z plots with improved accuracy.
1182
  """
1183
  if z_a <= 0 or y <= 0 or z_min >= z_max:
1184
  st.error("Invalid input parameters.")
@@ -1187,35 +1471,34 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1187
  # Apply the condition for y
1188
  y_effective = y if y > 1 else 1/y
1189
 
 
 
 
1190
  z_points = np.linspace(z_min, z_max, n_points)
1191
 
1192
  # Collect all roots first
1193
  all_roots = []
1194
  discriminants = []
1195
 
1196
- # Progress indicator
1197
- progress_bar = st.progress(0)
1198
- status_text = st.empty()
1199
-
1200
  for i, z in enumerate(z_points):
1201
  # Update progress
1202
- progress_bar.progress((i + 1) / n_points)
1203
- status_text.text(f"Computing roots for z = {z:.3f} ({i+1}/{n_points})")
1204
 
1205
- # Calculate roots
1206
  roots = compute_cubic_roots(z, beta, z_a, y)
1207
 
1208
  # Initial sorting to help with tracking
1209
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1210
  all_roots.append(roots)
1211
 
1212
- # Calculate discriminant
1213
  disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1214
  discriminants.append(disc)
1215
 
1216
- # Clear progress indicators
1217
- progress_bar.empty()
1218
- status_text.empty()
 
1219
 
1220
  all_roots = np.array(all_roots)
1221
  discriminants = np.array(discriminants)
@@ -1223,6 +1506,8 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1223
  # Track roots consistently across z values
1224
  tracked_roots = track_roots_consistently(z_points, all_roots)
1225
 
 
 
1226
  # Extract imaginary and real parts
1227
  ims = np.imag(tracked_roots)
1228
  res = np.real(tracked_roots)
@@ -1270,7 +1555,7 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1270
 
1271
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1272
  """
1273
- Generate Im(s) and Re(s) vs. β plots.
1274
  """
1275
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
1276
  st.error("Invalid input parameters.")
@@ -1279,35 +1564,34 @@ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1279
  # Apply the condition for y
1280
  y_effective = y if y > 1 else 1/y
1281
 
 
 
 
1282
  beta_points = np.linspace(beta_min, beta_max, n_points)
1283
 
1284
  # Collect all roots first
1285
  all_roots = []
1286
  discriminants = []
1287
 
1288
- # Progress indicator
1289
- progress_bar = st.progress(0)
1290
- status_text = st.empty()
1291
-
1292
  for i, beta in enumerate(beta_points):
1293
  # Update progress
1294
- progress_bar.progress((i + 1) / n_points)
1295
- status_text.text(f"Computing roots for β = {beta:.3f} ({i+1}/{n_points})")
1296
 
1297
- # Calculate roots
1298
  roots = compute_cubic_roots(z, beta, z_a, y)
1299
 
1300
  # Initial sorting to help with tracking
1301
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1302
  all_roots.append(roots)
1303
 
1304
- # Calculate discriminant
1305
  disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1306
  discriminants.append(disc)
1307
 
1308
- # Clear progress indicators
1309
- progress_bar.empty()
1310
- status_text.empty()
 
1311
 
1312
  all_roots = np.array(all_roots)
1313
  discriminants = np.array(discriminants)
@@ -1315,6 +1599,8 @@ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1315
  # Track roots consistently across beta values
1316
  tracked_roots = track_roots_consistently(beta_points, all_roots)
1317
 
 
 
1318
  # Extract imaginary and real parts
1319
  ims = np.imag(tracked_roots)
1320
  res = np.real(tracked_roots)
@@ -1363,13 +1649,16 @@ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1363
  def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_max=10.0,
1364
  beta_steps=100, z_steps=100):
1365
  """
1366
- Generate a phase diagram showing regions of complex and real roots.
1367
  """
1368
  if cpp_compiled:
1369
  phase_map = generate_phase_diagram_cpp(z_a, y, beta_min, beta_max, z_min, z_max, beta_steps, z_steps)
1370
  beta_values = np.linspace(beta_min, beta_max, beta_steps)
1371
  z_values = np.linspace(z_min, z_max, z_steps)
1372
  else:
 
 
 
1373
  # Apply the condition for y
1374
  y_effective = y if y > 1 else 1/y
1375
 
@@ -1379,26 +1668,19 @@ def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_ma
1379
  # Initialize phase map
1380
  phase_map = np.zeros((z_steps, beta_steps))
1381
 
1382
- # Progress tracking
1383
- progress_bar = st.progress(0)
1384
- status_text = st.empty()
1385
-
1386
  for i, z in enumerate(z_values):
1387
  # Update progress
1388
- progress_bar.progress((i + 1) / len(z_values))
1389
- status_text.text(f"Analyzing phase at z = {z:.2f} ({i+1}/{len(z_values)})")
1390
 
1391
  for j, beta in enumerate(beta_values):
1392
- roots = compute_cubic_roots(z, beta, z_a, y)
1393
-
1394
- # Check if all roots are real (imaginary parts close to zero)
1395
- is_all_real = all(abs(root.imag) < 1e-10 for root in roots)
1396
 
1397
- phase_map[i, j] = 1 if is_all_real else -1
 
 
1398
 
1399
- # Clear progress indicators
1400
- progress_bar.empty()
1401
- status_text.empty()
1402
 
1403
  # Create heatmap
1404
  fig = go.Figure(data=go.Heatmap(
@@ -1428,17 +1710,25 @@ def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_ma
1428
  def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
1429
  """
1430
  Generate the eigenvalue distribution of B_n = S_n T_n as n→∞
 
1431
  """
 
 
 
1432
  if cpp_compiled:
 
1433
  eigenvalues, x_vals = generate_eigenvalue_distribution_cpp(beta, y, z_a, n, seed)
 
1434
  else:
1435
  # Apply the condition for y
1436
  y_effective = y if y > 1 else 1/y
1437
 
1438
  # Set random seed
 
1439
  np.random.seed(seed)
1440
 
1441
  # Compute dimension p based on aspect ratio y
 
1442
  p = int(y_effective * n)
1443
 
1444
  # Constructing T_n (Population / Shape Matrix)
@@ -1451,24 +1741,31 @@ def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
1451
  T_n = np.diag(diag_entries)
1452
 
1453
  # Generate the data matrix X with i.i.d. standard normal entries
 
1454
  X = np.random.randn(p, n)
1455
 
1456
  # Compute the sample covariance matrix S_n = (1/n) * XX^T
 
1457
  S_n = (1 / n) * (X @ X.T)
1458
 
1459
  # Compute B_n = S_n T_n
 
1460
  B_n = S_n @ T_n
1461
 
1462
- # Compute eigenvalues of B_n
 
1463
  eigenvalues = np.linalg.eigvalsh(B_n)
1464
 
1465
  # Generate x values for KDE
1466
  x_vals = np.linspace(min(eigenvalues), max(eigenvalues), 500)
1467
 
1468
  # Use KDE to compute a smooth density estimate
 
1469
  kde = gaussian_kde(eigenvalues)
1470
  kde_vals = kde(x_vals)
1471
 
 
 
1472
  # Create figure
1473
  fig = go.Figure()
1474
 
@@ -1501,21 +1798,33 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
1501
  use_eigenvalue_method=True,
1502
  n_samples=1000,
1503
  seeds=5):
 
 
 
 
 
 
1504
  if z_a <= 0 or y <= 0 or z_min >= z_max:
 
1505
  st.error("Invalid input parameters.")
1506
  return None
1507
 
 
1508
  betas = np.linspace(0, 1, beta_steps)
1509
 
1510
  if use_eigenvalue_method:
1511
  # Use the eigenvalue method to compute boundaries
1512
- st.info("Computing eigenvalue support boundaries. This may take a moment...")
1513
  min_eigs, max_eigs = compute_eigenvalue_support_boundaries(z_a, y, betas, n_samples, seeds)
1514
  z_mins, z_maxs = min_eigs, max_eigs
1515
  else:
1516
  # Use the original discriminant method
 
1517
  betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps)
1518
-
 
 
 
1519
  high_y_curve = compute_high_y_curve(betas, z_a, y) if show_high_y else None
1520
  alt_low_expr = compute_alternate_low_expr(betas, z_a, y) if show_low_y else None
1521
 
@@ -1533,6 +1842,7 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
1533
 
1534
  # Compute derivatives if needed
1535
  if show_derivatives:
 
1536
  derivatives = compute_all_derivatives(betas, z_mins, z_maxs, None, high_y_curve,
1537
  alt_low_expr, custom_curve1, custom_curve2)
1538
  # Calculate derivatives for max_k and min_t curves if they exist
@@ -1540,6 +1850,8 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
1540
  max_k_derivatives = compute_derivatives(max_k_curve, betas)
1541
  if show_min_t and min_t_curve is not None:
1542
  min_t_derivatives = compute_derivatives(min_t_curve, betas)
 
 
1543
 
1544
  fig = go.Figure()
1545
 
@@ -1642,6 +1954,8 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
1642
  x=0.01
1643
  )
1644
  )
 
 
1645
  return fig
1646
 
1647
  def analyze_complex_root_structure(beta_values, z, z_a, y):
@@ -1653,6 +1967,9 @@ def analyze_complex_root_structure(beta_values, z, z_a, y):
1653
  - transition_points: beta values where the root structure changes
1654
  - structure_types: list indicating whether each interval has all real roots or complex roots
1655
  """
 
 
 
1656
  # Apply the condition for y
1657
  y_effective = y if y > 1 else 1/y
1658
 
@@ -1661,7 +1978,9 @@ def analyze_complex_root_structure(beta_values, z, z_a, y):
1661
 
1662
  previous_type = None
1663
 
1664
- for beta in beta_values:
 
 
1665
  roots = compute_cubic_roots(z, beta, z_a, y)
1666
 
1667
  # Check if all roots are real (imaginary parts close to zero)
@@ -1680,16 +1999,19 @@ def analyze_complex_root_structure(beta_values, z, z_a, y):
1680
  if previous_type is not None:
1681
  structure_types.append(previous_type)
1682
 
 
1683
  return transition_points, structure_types
1684
 
1685
  # ----------------- Streamlit UI -----------------
1686
  st.title("Cubic Root Analysis (C++ Accelerated)")
1687
 
1688
- # Add a note about C++ acceleration
1689
  if cpp_compiled:
1690
  st.success("✅ C++ acceleration module loaded successfully. Calculations will run faster!")
1691
  else:
1692
- st.warning("⚠️ C++ module compilation failed. Falling back to Python implementations which will be slower.")
 
 
1693
 
1694
  # Define three tabs
1695
  tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Complex Root Analysis", "Differential Analysis"])
@@ -1997,28 +2319,28 @@ with tab2:
1997
  st.markdown("### Comparison of Empirical vs Theoretical Bounds")
1998
  col1, col2, col3 = st.columns(3)
1999
  with col1:
2000
- st.metric("Theoretical Min", f"{min_eig[0]:.4f}")
2001
- st.metric("Theoretical Max", f"{max_eig[0]:.4f}")
2002
- st.metric("Theoretical Width", f"{max_eig[0] - min_eig[0]:.4f}")
2003
  with col2:
2004
- st.metric("Empirical Min", f"{empirical_min:.4f}")
2005
- st.metric("Empirical Max", f"{empirical_max:.4f}")
2006
- st.metric("Empirical Width", f"{empirical_max - empirical_min:.4f}")
2007
  with col3:
2008
- st.metric("Min Difference", f"{empirical_min - min_eig[0]:.4f}")
2009
- st.metric("Max Difference", f"{empirical_max - max_eig[0]:.4f}")
2010
- st.metric("Width Difference", f"{(empirical_max - empirical_min) - (max_eig[0] - min_eig[0]):.4f}")
2011
 
2012
  # Display additional statistics
2013
  if show_empirical_stats:
2014
  st.markdown("### Eigenvalue Statistics")
2015
  col1, col2 = st.columns(2)
2016
  with col1:
2017
- st.metric("Mean", f"{np.mean(eigenvalues):.4f}")
2018
- st.metric("Median", f"{np.median(eigenvalues):.4f}")
2019
  with col2:
2020
- st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}")
2021
- st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}")
2022
 
2023
  # ----- Tab 3: Differential Analysis -----
2024
  with tab3:
@@ -2064,18 +2386,25 @@ with tab3:
2064
  with col2:
2065
  use_eigenvalue_method_diff = (diff_method_type == "Eigenvalue Method")
2066
 
 
 
 
 
2067
  if use_eigenvalue_method_diff:
2068
  betas_diff = np.linspace(0, 1, beta_steps_diff)
2069
- st.info("Computing eigenvalue support boundaries. This may take a moment...")
2070
  lower_vals, upper_vals = compute_eigenvalue_support_boundaries(
2071
  z_a_diff, y_diff, betas_diff, diff_n_samples, diff_seeds)
2072
  else:
 
2073
  betas_diff, lower_vals, upper_vals = sweep_beta_and_find_z_bounds(
2074
  z_a_diff, y_diff, z_min_diff, z_max_diff, beta_steps_diff, z_steps_diff)
2075
 
2076
  # Create figure
 
2077
  fig_diff = go.Figure()
2078
 
 
2079
  if analyze_upper_lower:
2080
  diff_curve = upper_vals - lower_vals
2081
  d1 = np.gradient(diff_curve, betas_diff)
@@ -2112,6 +2441,7 @@ with tab3:
2112
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d2, mode="lines",
2113
  name="Low y d²/dβ²", line=dict(color="orange", dash='dot')))
2114
 
 
2115
  fig_diff.update_layout(
2116
  title="Differential Analysis vs. β" +
2117
  (" (Eigenvalue Method)" if use_eigenvalue_method_diff else " (Discriminant Method)"),
@@ -2126,6 +2456,8 @@ with tab3:
2126
  x=0.01
2127
  )
2128
  )
 
 
2129
  st.plotly_chart(fig_diff, use_container_width=True)
2130
 
2131
  with st.expander("Curve Types", expanded=False):
 
9
  import subprocess
10
  import sys
11
  import importlib.util
12
+ import time
13
+ from datetime import timedelta
14
 
15
  # Configure Streamlit for Hugging Face Spaces
16
  st.set_page_config(
 
19
  initial_sidebar_state="collapsed"
20
  )
21
 
22
+ # Create a class for advanced progress tracking
23
+ class AdvancedProgressBar:
24
+ def __init__(self, total_steps, description="Processing", auto_refresh=True):
25
+ self.total_steps = total_steps
26
+ self.current_step = 0
27
+ self.start_time = time.time()
28
+ self.description = description
29
+ self.auto_refresh = auto_refresh
30
+
31
+ # Create UI elements
32
+ self.status_container = st.empty()
33
+ self.progress_bar = st.progress(0)
34
+ self.metrics_cols = st.columns(4)
35
+ self.step_metric = self.metrics_cols[0].empty()
36
+ self.percent_metric = self.metrics_cols[1].empty()
37
+ self.elapsed_metric = self.metrics_cols[2].empty()
38
+ self.eta_metric = self.metrics_cols[3].empty()
39
+
40
+ # Initialize with starting values
41
+ self.update_status(f"Starting {self.description}...")
42
+ self.update(0)
43
+
44
+ def update(self, step=None, description=None):
45
+ if step is not None:
46
+ self.current_step = step
47
+ else:
48
+ self.current_step += 1
49
+
50
+ if description:
51
+ self.description = description
52
+
53
+ # Calculate progress percentage
54
+ progress = min(self.current_step / self.total_steps, 1.0)
55
+
56
+ # Update progress bar
57
+ self.progress_bar.progress(progress)
58
+
59
+ # Update metrics
60
+ elapsed = time.time() - self.start_time
61
+ elapsed_str = str(timedelta(seconds=int(elapsed)))
62
+
63
+ if progress > 0:
64
+ eta = elapsed * (1 - progress) / progress
65
+ eta_str = str(timedelta(seconds=int(eta)))
66
+ else:
67
+ eta_str = "Calculating..."
68
+
69
+ step_text = f"{self.current_step}/{self.total_steps}"
70
+ percent_text = f"{progress*100:.1f}%"
71
+
72
+ self.step_metric.metric("Steps", step_text)
73
+ self.percent_metric.metric("Progress", percent_text)
74
+ self.elapsed_metric.metric("Elapsed", elapsed_str)
75
+ self.eta_metric.metric("ETA", eta_str)
76
+
77
+ # Update status text
78
+ self.update_status(f"{self.description} - Step {self.current_step} of {self.total_steps}")
79
+
80
+ def update_status(self, text):
81
+ self.status_container.text(text)
82
+
83
+ def complete(self, success=True):
84
+ if success:
85
+ self.progress_bar.progress(1.0)
86
+ self.update_status(f"✅ {self.description} completed successfully!")
87
+ else:
88
+ self.update_status(f"❌ {self.description} failed or was interrupted.")
89
+
90
+ elapsed = time.time() - self.start_time
91
+ elapsed_str = str(timedelta(seconds=int(elapsed)))
92
+ self.elapsed_metric.metric("Total Time", elapsed_str)
93
+ self.eta_metric.metric("ETA", "Completed")
94
+
95
+ # Add small delay to show completion
96
+ time.sleep(0.5)
97
+
98
+ def clear(self):
99
+ self.status_container.empty()
100
+ self.progress_bar.empty()
101
+ self.step_metric.empty()
102
+ self.percent_metric.empty()
103
+ self.elapsed_metric.empty()
104
+ self.eta_metric.empty()
105
+
106
+ # Initialize sympy precision settings for higher accuracy
107
+ sp.mpmath.mp.dps = 50 # Set decimal precision to 50 digits
108
+
109
  # Check if C++ module is already compiled, otherwise compile it
110
  cpp_compiled = False
111
 
112
  def compile_cpp_module():
113
+ progress = AdvancedProgressBar(5, "Compiling C++ module")
114
+
115
  # Define C++ code as a string
116
  cpp_code = """
117
  #include <pybind11/pybind11.h>
 
124
  #include <random>
125
  #include <cmath>
126
  #include <algorithm>
127
+ #include <omp.h>
128
 
129
  namespace py = pybind11;
130
  using namespace Eigen;
 
149
  double* z_ptr = static_cast<double*>(z_buf.ptr);
150
  double* result_ptr = static_cast<double*>(result_buf.ptr);
151
 
152
+ #pragma omp parallel for
153
  for (size_t i = 0; i < z_buf.size; i++) {
154
  result_ptr[i] = compute_discriminant_fast(z_ptr[i], beta, z_a, y);
155
  }
 
735
  }
736
  """
737
 
738
+ progress.update(1, "Creating temporary directory for compilation")
739
+
740
  # Create a temporary directory to compile the C++ code
741
  with tempfile.TemporaryDirectory() as tmpdirname:
742
  # Write C++ code to file
743
+ progress.update(2, "Writing C++ code to temporary file")
744
  with open(os.path.join(tmpdirname, "cubic_cpp.cpp"), "w") as f:
745
  f.write(cpp_code)
746
 
747
  # Write setup.py for compiling with pybind11
748
+ progress.update(3, "Creating setup.py file")
749
  setup_py = """
750
  from setuptools import setup, Extension
751
  from pybind11.setup_helpers import Pybind11Extension, build_ext
 
772
  f.write(setup_py)
773
 
774
  # Compile the module
775
+ progress.update(4, "Running compilation process")
776
  try:
777
  result = subprocess.run(
778
  [sys.executable, "setup.py", "build_ext", "--inplace"],
 
790
  break
791
 
792
  if not module_path:
793
+ progress.complete(False)
794
  st.error("Failed to find compiled module.")
795
  st.code(result.stdout)
796
  st.code(result.stderr)
797
  return False
798
 
799
  # Import the module
800
+ progress.update(5, "Importing compiled module")
801
  spec = importlib.util.spec_from_file_location("cubic_cpp", module_path)
802
  cubic_cpp = importlib.util.module_from_spec(spec)
803
  spec.loader.exec_module(cubic_cpp)
 
814
  globals()["generate_eigenvalue_distribution_cpp"] = cubic_cpp.generate_eigenvalue_distribution_cpp
815
  globals()["generate_phase_diagram_cpp"] = cubic_cpp.generate_phase_diagram_cpp
816
 
817
+ progress.complete(True)
818
  return True
819
 
820
  except subprocess.CalledProcessError as e:
821
+ progress.complete(False)
822
  st.error(f"Compilation failed: {e}")
823
  st.code(e.stdout)
824
  st.code(e.stderr)
 
838
  Scan z in [z_min, z_max] for sign changes in the discriminant,
839
  and return approximated roots (where the discriminant is zero).
840
  """
841
+ # Create progress bar
842
+ progress = AdvancedProgressBar(steps, "Finding discriminant zeros")
843
+
844
  # Apply the condition for y
845
  y_effective = y if y > 1 else 1/y
846
 
847
+ # Symbolic variables for the cubic discriminant with higher precision
848
  z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True, positive=True)
849
 
850
  # Define coefficients a, b, c, d in terms of z_sym, beta_sym, z_a_sym, y_sym
 
853
  c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
854
  d_sym = 1
855
 
856
+ # Symbolic expression for the cubic discriminant using standard form
857
+ Delta_expr = 18*a_sym*b_sym*c_sym*d_sym - 27*a_sym**2*d_sym**2 + b_sym**2*c_sym**2 - 2*b_sym**3*d_sym - 9*a_sym*c_sym**3
 
 
 
858
 
859
  # Fast numeric function for the discriminant
860
+ discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "mpmath")
861
 
862
  z_grid = np.linspace(z_min, z_max, steps)
863
+ disc_vals = []
864
+
865
+ # Calculate discriminant values with progress tracking
866
+ for i, z in enumerate(z_grid):
867
+ progress.update(i+1, f"Computing discriminant at z = {z:.4f}")
868
+ disc_vals.append(float(discriminant_func(z, beta, z_a, y_effective)))
869
+
870
+ disc_vals = np.array(disc_vals)
871
  roots_found = []
872
+
873
+ # Find sign changes with high-precision refinement
874
+ progress.update_status("Finding and refining discriminant zero locations")
875
  for i in range(len(z_grid) - 1):
876
  f1, f2 = disc_vals[i], disc_vals[i+1]
877
  if np.isnan(f1) or np.isnan(f2):
878
  continue
879
+ if abs(f1) < 1e-12:
880
  roots_found.append(z_grid[i])
881
+ elif abs(f2) < 1e-12:
882
  roots_found.append(z_grid[i+1])
883
  elif f1 * f2 < 0:
884
+ # High-precision binary search for more accurate root
885
  zl, zr = z_grid[i], z_grid[i+1]
886
+ f1 = discriminant_func(zl, beta, z_a, y_effective)
887
+ f2 = discriminant_func(zr, beta, z_a, y_effective)
888
+
889
  for _ in range(50):
890
+ mid = sp.Float(0.5) * (zl + zr)
891
  fm = discriminant_func(mid, beta, z_a, y_effective)
892
+ if abs(fm) < 1e-15:
893
  zl = zr = mid
894
  break
895
+ if fm * f1 > 0:
896
  zl, f1 = mid, fm
897
  else:
898
  zr, f2 = mid, fm
899
+
900
+ # Convert back to float for NumPy compatibility
901
+ roots_found.append(float(0.5 * (zl + zr)))
902
+
903
+ progress.complete()
904
  return np.array(roots_found)
905
 
906
  @st.cache_data
 
913
  if cpp_compiled:
914
  return find_discriminant_zeros(z_a, y, z_min, z_max, beta_steps, z_steps)
915
 
916
+ # Create progress tracking
917
+ progress = AdvancedProgressBar(beta_steps, "Computing discriminant zeros across β values")
918
+
919
  betas = np.linspace(0, 1, beta_steps)
920
  z_min_values = []
921
  z_max_values = []
922
+
923
+ for i, b in enumerate(betas):
924
+ progress.update(i+1, f"Processing β = {b:.4f}")
925
  roots = find_z_at_discriminant_zero(z_a, y, b, z_min, z_max, z_steps)
926
  if len(roots) == 0:
927
  z_min_values.append(np.nan)
 
929
  else:
930
  z_min_values.append(np.min(roots))
931
  z_max_values.append(np.max(roots))
932
+
933
+ progress.complete()
934
  return betas, np.array(z_min_values), np.array(z_max_values)
935
 
936
  @st.cache_data
 
942
  if cpp_compiled:
943
  return compute_eigenvalue_boundaries(z_a, y, beta_values, n_samples, seeds)
944
 
945
+ # Create progress tracking
946
+ progress = AdvancedProgressBar(len(beta_values), "Computing eigenvalue support boundaries")
947
+
948
  # Apply the condition for y
949
  y_effective = y if y > 1 else 1/y
950
 
951
  min_eigenvalues = np.zeros_like(beta_values)
952
  max_eigenvalues = np.zeros_like(beta_values)
953
 
 
 
 
 
954
  for i, beta in enumerate(beta_values):
955
  # Update progress
956
+ progress.update(i+1, f"Processing β = {beta:.4f}")
 
957
 
958
  min_vals = []
959
  max_vals = []
 
996
  min_eigenvalues[i] = np.mean(min_vals)
997
  max_eigenvalues[i] = np.mean(max_vals)
998
 
999
+ progress.complete()
 
 
 
1000
  return min_eigenvalues, max_eigenvalues
1001
 
1002
  @st.cache_data
1003
  def compute_high_y_curve(betas, z_a, y):
1004
  """
1005
+ Compute the "High y Expression" curve with high precision.
1006
  """
1007
  if cpp_compiled:
1008
  return compute_high_y_curve(betas, z_a, y)
1009
 
1010
+ # Create progress tracking
1011
+ progress = AdvancedProgressBar(1, "Computing high y expression")
1012
+
1013
  # Apply the condition for y
1014
  y_effective = y if y > 1 else 1/y
1015
 
1016
+ # Use SymPy for higher precision
1017
+ beta_sym, z_a_sym, y_sym = sp.symbols("beta z_a y", positive=True)
1018
+ a = z_a_sym
1019
  denominator = 1 - 2*a
1020
+ numerator = -4*a*(a-1)*y_sym*beta_sym - 2*a*y_sym - 2*a*(2*a-1)
1021
+
1022
+ # Create the high precision expression
1023
+ expr = numerator / denominator
1024
+
1025
+ # Convert to a high-precision numeric function
1026
+ func = sp.lambdify((beta_sym, z_a_sym, y_sym), expr, "mpmath")
1027
+
1028
+ # Compute values with high precision
1029
+ result = np.zeros_like(betas)
1030
+ if abs(float(denominator.subs(z_a_sym, z_a))) < 1e-12:
1031
+ result.fill(np.nan)
1032
+ else:
1033
+ for i, beta in enumerate(betas):
1034
+ result[i] = float(func(beta, z_a, y_effective))
1035
+
1036
+ progress.complete()
1037
+ return result
1038
 
1039
  @st.cache_data
1040
  def compute_alternate_low_expr(betas, z_a, y):
1041
  """
1042
+ Compute the alternate low expression with high precision.
1043
  """
1044
  if cpp_compiled:
1045
  return compute_alternate_low_expr(betas, z_a, y)
1046
 
1047
+ # Create progress tracking
1048
+ progress = AdvancedProgressBar(1, "Computing low y expression")
1049
+
1050
  # Apply the condition for y
1051
  y_effective = y if y > 1 else 1/y
1052
 
1053
+ # Use SymPy for higher precision
1054
+ beta_sym, z_a_sym, y_sym = sp.symbols("beta z_a y", positive=True)
1055
+ expr = (z_a_sym * y_sym * beta_sym * (z_a_sym - 1) -
1056
+ 2*z_a_sym*(1 - y_sym) - 2*z_a_sym**2) / (2 + 2*z_a_sym)
1057
+
1058
+ # Convert to a high-precision numeric function
1059
+ func = sp.lambdify((beta_sym, z_a_sym, y_sym), expr, "mpmath")
1060
+
1061
+ # Compute values with high precision
1062
+ result = np.zeros_like(betas)
1063
+ for i, beta in enumerate(betas):
1064
+ result[i] = float(func(beta, z_a, y_effective))
1065
+
1066
+ progress.complete()
1067
+ return result
1068
 
1069
  @st.cache_data
1070
  def compute_max_k_expression(betas, z_a, y, k_samples=1000):
1071
  """
1072
  Compute max_{k ∈ (0,∞)} (y*beta*(a-1)*k + (a*k+1)*((y-1)*k-1)) / ((a*k+1)*(k^2+k))
1073
+ with high precision.
1074
  """
1075
  if cpp_compiled:
1076
  return compute_max_k_expression(betas, z_a, y, k_samples)
1077
 
1078
+ # Create progress tracking
1079
+ progress = AdvancedProgressBar(len(betas), "Computing max k expression")
1080
+
1081
  # Apply the condition for y
1082
  y_effective = y if y > 1 else 1/y
1083
 
1084
+ # Use SymPy for symbolic expression
1085
+ k_sym, beta_sym, z_a_sym, y_sym = sp.symbols("k beta z_a y", positive=True)
1086
+ a = z_a_sym
1087
+ numerator = y_sym*beta_sym*(a-1)*k_sym + (a*k_sym+1)*((y_sym-1)*k_sym-1)
1088
+ denominator = (a*k_sym+1)*(k_sym**2+k_sym)
1089
+ expr = numerator / denominator
1090
+
1091
+ # Convert to high-precision function
1092
+ func = sp.lambdify((k_sym, beta_sym, z_a_sym, y_sym), expr, "mpmath")
1093
+
1094
  # Sample k values on a logarithmic scale
1095
  k_values = np.logspace(-3, 3, k_samples)
1096
 
1097
  max_vals = np.zeros_like(betas)
1098
  for i, beta in enumerate(betas):
1099
+ progress.update(i+1, f"Processing β = {beta:.4f}")
1100
+
1101
  values = np.zeros_like(k_values)
1102
  for j, k in enumerate(k_values):
1103
+ try:
1104
+ val = float(func(k, beta, z_a, y_effective))
1105
+ if np.isfinite(val):
1106
+ values[j] = val
1107
+ else:
1108
+ values[j] = np.nan
1109
+ except (ZeroDivisionError, OverflowError):
1110
  values[j] = np.nan
 
 
1111
 
1112
  valid_indices = ~np.isnan(values)
1113
  if np.any(valid_indices):
1114
  max_vals[i] = np.max(values[valid_indices])
1115
  else:
1116
  max_vals[i] = np.nan
1117
+
1118
+ progress.complete()
1119
  return max_vals
1120
 
1121
  @st.cache_data
1122
  def compute_min_t_expression(betas, z_a, y, t_samples=1000):
1123
  """
1124
  Compute min_{t ∈ (-1/a, 0)} (y*beta*(a-1)*t + (a*t+1)*((y-1)*t-1)) / ((a*t+1)*(t^2+t))
1125
+ with high precision.
1126
  """
1127
  if cpp_compiled:
1128
  return compute_min_t_expression(betas, z_a, y, t_samples)
1129
 
1130
+ # Create progress tracking
1131
+ progress = AdvancedProgressBar(len(betas), "Computing min t expression")
1132
+
1133
  # Apply the condition for y
1134
  y_effective = y if y > 1 else 1/y
1135
 
1136
+ # Use SymPy for symbolic expression
1137
+ t_sym, beta_sym, z_a_sym, y_sym = sp.symbols("t beta z_a y")
1138
+ a = z_a_sym
1139
+ numerator = y_sym*beta_sym*(a-1)*t_sym + (a*t_sym+1)*((y_sym-1)*t_sym-1)
1140
+ denominator = (a*t_sym+1)*(t_sym**2+t_sym)
1141
+ expr = numerator / denominator
1142
+
1143
+ # Convert to high-precision function
1144
+ func = sp.lambdify((t_sym, beta_sym, z_a_sym, y_sym), expr, "mpmath")
1145
+
1146
  a = z_a
1147
  if a <= 0:
1148
+ progress.complete(False)
1149
  return np.full_like(betas, np.nan)
1150
 
1151
  lower_bound = -1/a + 1e-10 # Avoid division by zero
 
1153
 
1154
  min_vals = np.zeros_like(betas)
1155
  for i, beta in enumerate(betas):
1156
+ progress.update(i+1, f"Processing β = {beta:.4f}")
1157
+
1158
  values = np.zeros_like(t_values)
1159
  for j, t in enumerate(t_values):
1160
+ try:
1161
+ val = float(func(t, beta, z_a, y_effective))
1162
+ if np.isfinite(val):
1163
+ values[j] = val
1164
+ else:
1165
+ values[j] = np.nan
1166
+ except (ZeroDivisionError, OverflowError):
1167
  values[j] = np.nan
 
 
1168
 
1169
  valid_indices = ~np.isnan(values)
1170
  if np.any(valid_indices):
1171
  min_vals[i] = np.min(values[valid_indices])
1172
  else:
1173
  min_vals[i] = np.nan
1174
+
1175
+ progress.complete()
1176
  return min_vals
1177
 
1178
  @st.cache_data
1179
  def compute_derivatives(curve, betas):
1180
+ """Compute first and second derivatives of a curve using SymPy for accuracy."""
1181
+ # Create a spline representation for smoother derivatives
1182
+ from scipy.interpolate import CubicSpline
1183
+
1184
+ # Filter out NaN values
1185
+ valid_idx = ~np.isnan(curve)
1186
+ if not np.any(valid_idx):
1187
+ return np.full_like(betas, np.nan), np.full_like(betas, np.nan)
1188
+
1189
+ valid_betas = betas[valid_idx]
1190
+ valid_curve = curve[valid_idx]
1191
+
1192
+ if len(valid_betas) < 4: # Need at least 4 points for cubic spline
1193
+ # Fall back to numpy gradient
1194
+ d1 = np.gradient(curve, betas)
1195
+ d2 = np.gradient(d1, betas)
1196
+ return d1, d2
1197
+
1198
+ # Create cubic spline for smoother derivatives
1199
+ cs = CubicSpline(valid_betas, valid_curve)
1200
+
1201
+ # Evaluate first and second derivatives
1202
+ d1 = np.zeros_like(betas)
1203
+ d2 = np.zeros_like(betas)
1204
+
1205
+ for i, beta in enumerate(betas):
1206
+ if np.isnan(curve[i]):
1207
+ d1[i] = np.nan
1208
+ d2[i] = np.nan
1209
+ else:
1210
+ d1[i] = cs(beta, 1) # First derivative
1211
+ d2[i] = cs(beta, 2) # Second derivative
1212
+
1213
  return d1, d2
1214
 
1215
  def compute_all_derivatives(betas, z_mins, z_maxs, low_y_curve, high_y_curve, alt_low_expr, custom_curve1=None, custom_curve2=None):
1216
  """Compute derivatives for all curves"""
1217
+ progress = AdvancedProgressBar(7, "Computing derivatives")
1218
+
1219
  derivatives = {}
1220
 
1221
  # Upper z*(β)
1222
+ progress.update(1, "Computing upper z*(β) derivatives")
1223
  derivatives['upper'] = compute_derivatives(z_maxs, betas)
1224
 
1225
  # Lower z*(β)
1226
+ progress.update(2, "Computing lower z*(β) derivatives")
1227
  derivatives['lower'] = compute_derivatives(z_mins, betas)
1228
 
1229
  # Low y Expression (only if provided)
1230
  if low_y_curve is not None:
1231
+ progress.update(3, "Computing low y expression derivatives")
1232
  derivatives['low_y'] = compute_derivatives(low_y_curve, betas)
1233
+ else:
1234
+ progress.update(3, "Skipping low y expression (not provided)")
1235
 
1236
  # High y Expression
1237
  if high_y_curve is not None:
1238
+ progress.update(4, "Computing high y expression derivatives")
1239
  derivatives['high_y'] = compute_derivatives(high_y_curve, betas)
1240
+ else:
1241
+ progress.update(4, "Skipping high y expression (not provided)")
1242
 
1243
  # Alternate Low Expression
1244
  if alt_low_expr is not None:
1245
+ progress.update(5, "Computing alternate low expression derivatives")
1246
  derivatives['alt_low'] = compute_derivatives(alt_low_expr, betas)
1247
+ else:
1248
+ progress.update(5, "Skipping alternate low expression (not provided)")
1249
 
1250
  # Custom Expression 1 (if provided)
1251
  if custom_curve1 is not None:
1252
+ progress.update(6, "Computing custom expression 1 derivatives")
1253
  derivatives['custom1'] = compute_derivatives(custom_curve1, betas)
1254
+ else:
1255
+ progress.update(6, "Skipping custom expression 1 (not provided)")
1256
 
1257
  # Custom Expression 2 (if provided)
1258
  if custom_curve2 is not None:
1259
+ progress.update(7, "Computing custom expression 2 derivatives")
1260
  derivatives['custom2'] = compute_derivatives(custom_curve2, betas)
1261
+ else:
1262
+ progress.update(7, "Skipping custom expression 2 (not provided)")
1263
+
1264
+ progress.complete()
1265
  return derivatives
1266
 
1267
  def compute_custom_expression(betas, z_a, y, s_num_expr, s_denom_expr, is_s_based=True):
1268
  """
1269
+ Compute custom curve with high precision using SymPy.
1270
+ If is_s_based=True, compute using s substitution. Otherwise, compute direct z(β) expression.
1271
  """
1272
+ progress = AdvancedProgressBar(4, "Computing custom expression")
1273
+
1274
  # Apply the condition for y
1275
  y_effective = y if y > 1 else 1/y
1276
 
1277
+ # Create SymPy symbols
1278
  beta_sym, z_a_sym, y_sym = sp.symbols("beta z_a y", positive=True)
1279
  local_dict = {"beta": beta_sym, "z_a": z_a_sym, "y": y_sym, "sp": sp}
1280
 
1281
  try:
1282
  # Add sqrt support
1283
+ progress.update(1, "Parsing expression")
1284
  s_num_expr = add_sqrt_support(s_num_expr)
1285
  s_denom_expr = add_sqrt_support(s_denom_expr)
1286
 
 
1289
 
1290
  if is_s_based:
1291
  # Compute s and substitute into main expression
1292
+ progress.update(2, "Computing s-based expression")
1293
  s_expr = num_expr / denom_expr
1294
  a = z_a_sym
1295
  numerator = y_sym*beta_sym*(z_a_sym-1)*s_expr + (a*s_expr+1)*((y_sym-1)*s_expr-1)
 
1297
  final_expr = numerator/denominator
1298
  else:
1299
  # Direct z(β) expression
1300
+ progress.update(2, "Computing direct z(β) expression")
1301
  final_expr = num_expr / denom_expr
1302
 
1303
  except sp.SympifyError as e:
1304
+ progress.complete(False)
1305
  st.error(f"Error parsing expressions: {e}")
1306
  return np.full_like(betas, np.nan)
1307
 
1308
+ progress.update(3, "Creating lambda function")
1309
+
1310
+ # Convert to high-precision numeric function
1311
+ final_func = sp.lambdify((beta_sym, z_a_sym, y_sym), final_expr, modules=["mpmath"])
1312
+
1313
+ progress.update(4, "Evaluating expression")
1314
+
1315
+ # Compute values for each beta
1316
+ result = np.zeros_like(betas)
1317
+
1318
+ for i, beta in enumerate(betas):
1319
+ try:
1320
+ # Calculate with high precision
1321
+ val = final_func(beta, z_a, y_effective)
1322
+ # Convert to float for compatibility
1323
+ result[i] = float(val)
1324
+ except Exception as e:
1325
+ result[i] = np.nan
1326
+
1327
+ progress.complete()
1328
  return result
1329
 
1330
  def compute_cubic_roots(z, beta, z_a, y):
1331
  """
1332
+ Compute the roots of the cubic equation for given parameters with high precision using SymPy.
1333
  """
1334
  if cpp_compiled:
1335
  roots = compute_cubic_roots_cpp(z, beta, z_a, y)
 
1338
  # Apply the condition for y
1339
  y_effective = y if y > 1 else 1/y
1340
 
 
 
 
1341
  # Create a symbolic variable for the equation
1342
+ s = sp.Symbol('s')
1343
 
1344
  # Coefficients in the form as^3 + bs^2 + cs + d = 0
1345
  a = z * z_a
 
1348
  d = 1
1349
 
1350
  # Handle special cases
1351
+ if abs(a) < 1e-12:
1352
+ if abs(b) < 1e-12: # Linear case
1353
  roots = np.array([-d/c, 0, 0], dtype=complex)
1354
  else: # Quadratic case
1355
+ # Use SymPy for higher precision
1356
+ quad_eq = b*s**2 + c*s + d
1357
+ symbolic_roots = sp.solve(quad_eq, s)
1358
+ numerical_roots = [complex(float(sp.N(root.evalf(50)).real),
1359
+ float(sp.N(root.evalf(50)).imag)) for root in symbolic_roots]
1360
+ roots = np.array(numerical_roots + [0], dtype=complex)
1361
  return roots
1362
 
1363
  try:
1364
+ # Create the cubic equation with high precision
1365
+ cubic_eq = a*s**3 + b*s**2 + c*s + d
1366
 
1367
+ # Solve using SymPy's solver with high precision
1368
+ symbolic_roots = sp.solve(cubic_eq, s)
1369
 
1370
+ # Convert to high-precision complex numbers
1371
  numerical_roots = []
1372
  for root in symbolic_roots:
1373
+ # Use SymPy's N function with high precision (50 digits)
1374
+ high_prec_root = root.evalf(50)
1375
+ numerical_root = complex(float(sp.re(high_prec_root)), float(sp.im(high_prec_root)))
1376
  numerical_roots.append(numerical_root)
1377
 
1378
  # If we got fewer than 3 roots (due to multiplicity), pad with zeros
 
1429
 
1430
  def generate_cubic_discriminant(z, beta, z_a, y_effective):
1431
  """
1432
+ Calculate the cubic discriminant with high precision using SymPy's standard formula.
1433
+ For a cubic ax^3 + bx^2 + cx + d: Δ = 18abcd - 27a^2d^2 + b^2c^2 - 2b^3d - 9ac^3
 
1434
  """
1435
+ # Create symbolic variables for more accurate calculation
1436
+ z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True)
1437
+
1438
+ # Define coefficients with symbols
1439
+ a_sym = z_sym * z_a_sym
1440
+ b_sym = z_sym * z_a_sym + z_sym + z_a_sym - z_a_sym*y_sym
1441
+ c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
1442
+ d_sym = 1
1443
 
1444
  # Standard formula for cubic discriminant
1445
+ discriminant_expr = (
1446
+ 18*a_sym*b_sym*c_sym*d_sym -
1447
+ 27*a_sym**2*d_sym**2 +
1448
+ b_sym**2*c_sym**2 -
1449
+ 2*b_sym**3*d_sym -
1450
+ 9*a_sym*c_sym**3
1451
+ )
1452
+
1453
+ # Create a high-precision lambda function
1454
+ discriminant_func = sp.lambdify(
1455
+ (z_sym, beta_sym, z_a_sym, y_sym),
1456
+ discriminant_expr,
1457
+ modules="mpmath"
1458
+ )
1459
+
1460
+ # Evaluate with high precision
1461
+ return float(discriminant_func(z, beta, z_a, y_effective))
1462
 
1463
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
1464
  """
1465
+ Generate Im(s) and Re(s) vs. z plots with improved accuracy using SymPy.
1466
  """
1467
  if z_a <= 0 or y <= 0 or z_min >= z_max:
1468
  st.error("Invalid input parameters.")
 
1471
  # Apply the condition for y
1472
  y_effective = y if y > 1 else 1/y
1473
 
1474
+ # Create progress bar
1475
+ progress = AdvancedProgressBar(n_points, "Computing cubic roots vs. z")
1476
+
1477
  z_points = np.linspace(z_min, z_max, n_points)
1478
 
1479
  # Collect all roots first
1480
  all_roots = []
1481
  discriminants = []
1482
 
 
 
 
 
1483
  for i, z in enumerate(z_points):
1484
  # Update progress
1485
+ progress.update(i+1, f"Computing roots for z = {z:.3f}")
 
1486
 
1487
+ # Calculate roots using SymPy
1488
  roots = compute_cubic_roots(z, beta, z_a, y)
1489
 
1490
  # Initial sorting to help with tracking
1491
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1492
  all_roots.append(roots)
1493
 
1494
+ # Calculate discriminant with high precision
1495
  disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1496
  discriminants.append(disc)
1497
 
1498
+ progress.complete()
1499
+
1500
+ # Create secondary progress bar for root tracking
1501
+ track_progress = AdvancedProgressBar(1, "Tracking roots consistently across z values")
1502
 
1503
  all_roots = np.array(all_roots)
1504
  discriminants = np.array(discriminants)
 
1506
  # Track roots consistently across z values
1507
  tracked_roots = track_roots_consistently(z_points, all_roots)
1508
 
1509
+ track_progress.complete()
1510
+
1511
  # Extract imaginary and real parts
1512
  ims = np.imag(tracked_roots)
1513
  res = np.real(tracked_roots)
 
1555
 
1556
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
1557
  """
1558
+ Generate Im(s) and Re(s) vs. β plots with improved accuracy using SymPy.
1559
  """
1560
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
1561
  st.error("Invalid input parameters.")
 
1564
  # Apply the condition for y
1565
  y_effective = y if y > 1 else 1/y
1566
 
1567
+ # Create progress bar
1568
+ progress = AdvancedProgressBar(n_points, "Computing cubic roots vs. β")
1569
+
1570
  beta_points = np.linspace(beta_min, beta_max, n_points)
1571
 
1572
  # Collect all roots first
1573
  all_roots = []
1574
  discriminants = []
1575
 
 
 
 
 
1576
  for i, beta in enumerate(beta_points):
1577
  # Update progress
1578
+ progress.update(i+1, f"Computing roots for β = {beta:.3f}")
 
1579
 
1580
+ # Calculate roots using SymPy for higher precision
1581
  roots = compute_cubic_roots(z, beta, z_a, y)
1582
 
1583
  # Initial sorting to help with tracking
1584
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
1585
  all_roots.append(roots)
1586
 
1587
+ # Calculate discriminant with high precision
1588
  disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
1589
  discriminants.append(disc)
1590
 
1591
+ progress.complete()
1592
+
1593
+ # Create secondary progress bar for root tracking
1594
+ track_progress = AdvancedProgressBar(1, "Tracking roots consistently across β values")
1595
 
1596
  all_roots = np.array(all_roots)
1597
  discriminants = np.array(discriminants)
 
1599
  # Track roots consistently across beta values
1600
  tracked_roots = track_roots_consistently(beta_points, all_roots)
1601
 
1602
+ track_progress.complete()
1603
+
1604
  # Extract imaginary and real parts
1605
  ims = np.imag(tracked_roots)
1606
  res = np.real(tracked_roots)
 
1649
  def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_max=10.0,
1650
  beta_steps=100, z_steps=100):
1651
  """
1652
+ Generate a phase diagram showing regions of complex and real roots with high precision.
1653
  """
1654
  if cpp_compiled:
1655
  phase_map = generate_phase_diagram_cpp(z_a, y, beta_min, beta_max, z_min, z_max, beta_steps, z_steps)
1656
  beta_values = np.linspace(beta_min, beta_max, beta_steps)
1657
  z_values = np.linspace(z_min, z_max, z_steps)
1658
  else:
1659
+ # Create progress tracking
1660
+ progress = AdvancedProgressBar(z_steps, "Generating phase diagram")
1661
+
1662
  # Apply the condition for y
1663
  y_effective = y if y > 1 else 1/y
1664
 
 
1668
  # Initialize phase map
1669
  phase_map = np.zeros((z_steps, beta_steps))
1670
 
 
 
 
 
1671
  for i, z in enumerate(z_values):
1672
  # Update progress
1673
+ progress.update(i+1, f"Analyzing z = {z:.2f}")
 
1674
 
1675
  for j, beta in enumerate(beta_values):
1676
+ # Calculate discriminant with high precision
1677
+ disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
 
 
1678
 
1679
+ # Set result based on sign of discriminant
1680
+ # 1 for all real roots (discriminant > 0), -1 for complex roots (discriminant < 0)
1681
+ phase_map[i, j] = 1 if disc > 0 else -1
1682
 
1683
+ progress.complete()
 
 
1684
 
1685
  # Create heatmap
1686
  fig = go.Figure(data=go.Heatmap(
 
1710
  def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
1711
  """
1712
  Generate the eigenvalue distribution of B_n = S_n T_n as n→∞
1713
+ with high precision.
1714
  """
1715
+ # Create progress tracking
1716
+ progress = AdvancedProgressBar(7, "Computing eigenvalue distribution")
1717
+
1718
  if cpp_compiled:
1719
+ progress.update(1, "Using C++ accelerated implementation")
1720
  eigenvalues, x_vals = generate_eigenvalue_distribution_cpp(beta, y, z_a, n, seed)
1721
+ progress.update(6, "Eigenvalues computed successfully")
1722
  else:
1723
  # Apply the condition for y
1724
  y_effective = y if y > 1 else 1/y
1725
 
1726
  # Set random seed
1727
+ progress.update(1, "Setting up random seed")
1728
  np.random.seed(seed)
1729
 
1730
  # Compute dimension p based on aspect ratio y
1731
+ progress.update(2, "Initializing matrices")
1732
  p = int(y_effective * n)
1733
 
1734
  # Constructing T_n (Population / Shape Matrix)
 
1741
  T_n = np.diag(diag_entries)
1742
 
1743
  # Generate the data matrix X with i.i.d. standard normal entries
1744
+ progress.update(3, "Generating random data matrix")
1745
  X = np.random.randn(p, n)
1746
 
1747
  # Compute the sample covariance matrix S_n = (1/n) * XX^T
1748
+ progress.update(4, "Computing sample covariance matrix")
1749
  S_n = (1 / n) * (X @ X.T)
1750
 
1751
  # Compute B_n = S_n T_n
1752
+ progress.update(5, "Computing B_n matrix")
1753
  B_n = S_n @ T_n
1754
 
1755
+ # Compute eigenvalues of B_n with high precision
1756
+ progress.update(6, "Computing eigenvalues")
1757
  eigenvalues = np.linalg.eigvalsh(B_n)
1758
 
1759
  # Generate x values for KDE
1760
  x_vals = np.linspace(min(eigenvalues), max(eigenvalues), 500)
1761
 
1762
  # Use KDE to compute a smooth density estimate
1763
+ progress.update(7, "Computing kernel density estimate")
1764
  kde = gaussian_kde(eigenvalues)
1765
  kde_vals = kde(x_vals)
1766
 
1767
+ progress.complete()
1768
+
1769
  # Create figure
1770
  fig = go.Figure()
1771
 
 
1798
  use_eigenvalue_method=True,
1799
  n_samples=1000,
1800
  seeds=5):
1801
+ """
1802
+ Generate z vs beta plot with high precision calculations.
1803
+ """
1804
+ # Create main progress tracking
1805
+ main_progress = AdvancedProgressBar(5, "Computing z*(β) curves")
1806
+
1807
  if z_a <= 0 or y <= 0 or z_min >= z_max:
1808
+ main_progress.complete(False)
1809
  st.error("Invalid input parameters.")
1810
  return None
1811
 
1812
+ main_progress.update(1, "Creating β grid")
1813
  betas = np.linspace(0, 1, beta_steps)
1814
 
1815
  if use_eigenvalue_method:
1816
  # Use the eigenvalue method to compute boundaries
1817
+ main_progress.update(2, "Computing eigenvalue support boundaries")
1818
  min_eigs, max_eigs = compute_eigenvalue_support_boundaries(z_a, y, betas, n_samples, seeds)
1819
  z_mins, z_maxs = min_eigs, max_eigs
1820
  else:
1821
  # Use the original discriminant method
1822
+ main_progress.update(2, "Computing discriminant zeros")
1823
  betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps)
1824
+
1825
+ main_progress.update(3, "Computing additional curves")
1826
+
1827
+ # Compute additional curves
1828
  high_y_curve = compute_high_y_curve(betas, z_a, y) if show_high_y else None
1829
  alt_low_expr = compute_alternate_low_expr(betas, z_a, y) if show_low_y else None
1830
 
 
1842
 
1843
  # Compute derivatives if needed
1844
  if show_derivatives:
1845
+ main_progress.update(4, "Computing derivatives")
1846
  derivatives = compute_all_derivatives(betas, z_mins, z_maxs, None, high_y_curve,
1847
  alt_low_expr, custom_curve1, custom_curve2)
1848
  # Calculate derivatives for max_k and min_t curves if they exist
 
1850
  max_k_derivatives = compute_derivatives(max_k_curve, betas)
1851
  if show_min_t and min_t_curve is not None:
1852
  min_t_derivatives = compute_derivatives(min_t_curve, betas)
1853
+
1854
+ main_progress.update(5, "Creating plot")
1855
 
1856
  fig = go.Figure()
1857
 
 
1954
  x=0.01
1955
  )
1956
  )
1957
+
1958
+ main_progress.complete()
1959
  return fig
1960
 
1961
  def analyze_complex_root_structure(beta_values, z, z_a, y):
 
1967
  - transition_points: beta values where the root structure changes
1968
  - structure_types: list indicating whether each interval has all real roots or complex roots
1969
  """
1970
+ # Create progress tracking
1971
+ progress = AdvancedProgressBar(len(beta_values), "Analyzing root structure")
1972
+
1973
  # Apply the condition for y
1974
  y_effective = y if y > 1 else 1/y
1975
 
 
1978
 
1979
  previous_type = None
1980
 
1981
+ for i, beta in enumerate(beta_values):
1982
+ progress.update(i+1, f"Analyzing root structure at β = {beta:.4f}")
1983
+
1984
  roots = compute_cubic_roots(z, beta, z_a, y)
1985
 
1986
  # Check if all roots are real (imaginary parts close to zero)
 
1999
  if previous_type is not None:
2000
  structure_types.append(previous_type)
2001
 
2002
+ progress.complete()
2003
  return transition_points, structure_types
2004
 
2005
  # ----------------- Streamlit UI -----------------
2006
  st.title("Cubic Root Analysis (C++ Accelerated)")
2007
 
2008
+ # Add a note about C++ acceleration and high precision
2009
  if cpp_compiled:
2010
  st.success("✅ C++ acceleration module loaded successfully. Calculations will run faster!")
2011
  else:
2012
+ st.warning("⚠️ C++ module compilation failed. Falling back to Python implementations with high precision SymPy calculations.")
2013
+
2014
+ st.info(f"Using SymPy with {sp.mpmath.mp.dps} decimal digits of precision for accurate calculations.")
2015
 
2016
  # Define three tabs
2017
  tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Complex Root Analysis", "Differential Analysis"])
 
2319
  st.markdown("### Comparison of Empirical vs Theoretical Bounds")
2320
  col1, col2, col3 = st.columns(3)
2321
  with col1:
2322
+ st.metric("Theoretical Min", f"{min_eig[0]:.6f}")
2323
+ st.metric("Theoretical Max", f"{max_eig[0]:.6f}")
2324
+ st.metric("Theoretical Width", f"{max_eig[0] - min_eig[0]:.6f}")
2325
  with col2:
2326
+ st.metric("Empirical Min", f"{empirical_min:.6f}")
2327
+ st.metric("Empirical Max", f"{empirical_max:.6f}")
2328
+ st.metric("Empirical Width", f"{empirical_max - empirical_min:.6f}")
2329
  with col3:
2330
+ st.metric("Min Difference", f"{empirical_min - min_eig[0]:.6f}")
2331
+ st.metric("Max Difference", f"{empirical_max - max_eig[0]:.6f}")
2332
+ st.metric("Width Difference", f"{(empirical_max - empirical_min) - (max_eig[0] - min_eig[0]):.6f}")
2333
 
2334
  # Display additional statistics
2335
  if show_empirical_stats:
2336
  st.markdown("### Eigenvalue Statistics")
2337
  col1, col2 = st.columns(2)
2338
  with col1:
2339
+ st.metric("Mean", f"{np.mean(eigenvalues):.6f}")
2340
+ st.metric("Median", f"{np.median(eigenvalues):.6f}")
2341
  with col2:
2342
+ st.metric("Standard Deviation", f"{np.std(eigenvalues):.6f}")
2343
+ st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.6f}")
2344
 
2345
  # ----- Tab 3: Differential Analysis -----
2346
  with tab3:
 
2386
  with col2:
2387
  use_eigenvalue_method_diff = (diff_method_type == "Eigenvalue Method")
2388
 
2389
+ # Create a progress tracker
2390
+ progress = AdvancedProgressBar(5, "Computing differential analysis")
2391
+
2392
+ progress.update(1, "Setting up β grid")
2393
  if use_eigenvalue_method_diff:
2394
  betas_diff = np.linspace(0, 1, beta_steps_diff)
2395
+ progress.update(2, "Computing eigenvalue support boundaries")
2396
  lower_vals, upper_vals = compute_eigenvalue_support_boundaries(
2397
  z_a_diff, y_diff, betas_diff, diff_n_samples, diff_seeds)
2398
  else:
2399
+ progress.update(2, "Computing discriminant zeros")
2400
  betas_diff, lower_vals, upper_vals = sweep_beta_and_find_z_bounds(
2401
  z_a_diff, y_diff, z_min_diff, z_max_diff, beta_steps_diff, z_steps_diff)
2402
 
2403
  # Create figure
2404
+ progress.update(3, "Creating plot")
2405
  fig_diff = go.Figure()
2406
 
2407
+ progress.update(4, "Computing derivatives")
2408
  if analyze_upper_lower:
2409
  diff_curve = upper_vals - lower_vals
2410
  d1 = np.gradient(diff_curve, betas_diff)
 
2441
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d2, mode="lines",
2442
  name="Low y d²/dβ²", line=dict(color="orange", dash='dot')))
2443
 
2444
+ progress.update(5, "Finalizing plot")
2445
  fig_diff.update_layout(
2446
  title="Differential Analysis vs. β" +
2447
  (" (Eigenvalue Method)" if use_eigenvalue_method_diff else " (Discriminant Method)"),
 
2456
  x=0.01
2457
  )
2458
  )
2459
+
2460
+ progress.complete()
2461
  st.plotly_chart(fig_diff, use_container_width=True)
2462
 
2463
  with st.expander("Curve Types", expanded=False):