Spaces:
Sleeping
Sleeping
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):
|