Spaces:
Running
Running
Update app.py
Browse files
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 |
-
|
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 |
-
|
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, "
|
761 |
|
762 |
z_grid = np.linspace(z_min, z_max, steps)
|
763 |
-
disc_vals =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
770 |
roots_found.append(z_grid[i])
|
771 |
-
elif f2
|
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
|
779 |
zl = zr = mid
|
780 |
break
|
781 |
-
if
|
782 |
zl, f1 = mid, fm
|
783 |
else:
|
784 |
zr, f2 = mid, fm
|
785 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
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 |
-
|
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 |
-
|
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 |
-
|
894 |
-
|
|
|
895 |
denominator = 1 - 2*a
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
913 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
935 |
-
|
936 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
972 |
-
|
973 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
990 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
-
|
1060 |
-
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 =
|
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-
|
1091 |
-
if abs(b) < 1e-
|
1092 |
roots = np.array([-d/c, 0, 0], dtype=complex)
|
1093 |
else: # Quadratic case
|
1094 |
-
|
1095 |
-
|
|
|
|
|
|
|
|
|
1096 |
return roots
|
1097 |
|
1098 |
try:
|
1099 |
-
# Create the cubic
|
1100 |
-
cubic_eq =
|
1101 |
|
1102 |
-
# Solve
|
1103 |
-
symbolic_roots = solve(cubic_eq, s)
|
1104 |
|
1105 |
-
# Convert
|
1106 |
numerical_roots = []
|
1107 |
for root in symbolic_roots:
|
1108 |
-
# Use SymPy's N function with high precision
|
1109 |
-
|
|
|
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
|
1167 |
-
For a cubic ax^3 + bx^2 + cx + d:
|
1168 |
-
Δ = 18abcd - 27a^2d^2 + b^2c^2 - 2b^3d - 9ac^3
|
1169 |
"""
|
1170 |
-
|
1171 |
-
|
1172 |
-
|
1173 |
-
|
|
|
|
|
|
|
|
|
1174 |
|
1175 |
# Standard formula for cubic discriminant
|
1176 |
-
|
1177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
1217 |
-
|
1218 |
-
|
|
|
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 |
-
|
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 |
-
|
1309 |
-
|
1310 |
-
|
|
|
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 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
1398 |
|
1399 |
-
|
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 |
-
|
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
|
|
|
|
|
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]:.
|
2001 |
-
st.metric("Theoretical Max", f"{max_eig[0]:.
|
2002 |
-
st.metric("Theoretical Width", f"{max_eig[0] - min_eig[0]:.
|
2003 |
with col2:
|
2004 |
-
st.metric("Empirical Min", f"{empirical_min:.
|
2005 |
-
st.metric("Empirical Max", f"{empirical_max:.
|
2006 |
-
st.metric("Empirical Width", f"{empirical_max - empirical_min:.
|
2007 |
with col3:
|
2008 |
-
st.metric("Min Difference", f"{empirical_min - min_eig[0]:.
|
2009 |
-
st.metric("Max Difference", f"{empirical_max - max_eig[0]:.
|
2010 |
-
st.metric("Width Difference", f"{(empirical_max - empirical_min) - (max_eig[0] - min_eig[0]):.
|
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):.
|
2018 |
-
st.metric("Median", f"{np.median(eigenvalues):.
|
2019 |
with col2:
|
2020 |
-
st.metric("Standard Deviation", f"{np.std(eigenvalues):.
|
2021 |
-
st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.
|
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 |
-
|
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):
|