diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -19,7 +19,7 @@ from scipy.stats import gaussian_kde # Set page config with wider layout st.set_page_config( page_title="Matrix Analysis Dashboard", - page_icon="chart", + page_icon="πŸ“ˆ", layout="wide", initial_sidebar_state="expanded" ) @@ -238,675 +238,9 @@ def safe_convert_to_numeric(value): # Check if C++ source file exists if not os.path.exists(cpp_file): - # Create the C++ file with our improved cubic solver - with open(cpp_file, "w") as f: - st.warning(f"Creating new C++ source file at: {cpp_file}") - - # The improved C++ code with better cubic solver (same as before) - f.write(''' -// app.cpp - Modified version with improved cubic solver -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Struct to hold cubic equation roots -struct CubicRoots { - std::complex root1; - std::complex root2; - std::complex root3; -}; - -// Function to solve cubic equation: az^3 + bz^2 + cz + d = 0 -// Improved implementation based on ACM TOMS Algorithm 954 -CubicRoots solveCubic(double a, double b, double c, double d) { - // Declare roots structure at the beginning of the function - CubicRoots roots; - - // Constants for numerical stability - const double epsilon = 1e-14; - const double zero_threshold = 1e-10; - - // Handle special case for a == 0 (quadratic) - if (std::abs(a) < epsilon) { - // Quadratic equation handling (unchanged) - if (std::abs(b) < epsilon) { // Linear equation or constant - if (std::abs(c) < epsilon) { // Constant - no finite roots - roots.root1 = std::complex(std::numeric_limits::quiet_NaN(), 0.0); - roots.root2 = std::complex(std::numeric_limits::quiet_NaN(), 0.0); - roots.root3 = std::complex(std::numeric_limits::quiet_NaN(), 0.0); - } else { // Linear equation - roots.root1 = std::complex(-d / c, 0.0); - roots.root2 = std::complex(std::numeric_limits::infinity(), 0.0); - roots.root3 = std::complex(std::numeric_limits::infinity(), 0.0); - } - return roots; - } - - double discriminant = c * c - 4.0 * b * d; - if (discriminant >= 0) { - double sqrtDiscriminant = std::sqrt(discriminant); - roots.root1 = std::complex((-c + sqrtDiscriminant) / (2.0 * b), 0.0); - roots.root2 = std::complex((-c - sqrtDiscriminant) / (2.0 * b), 0.0); - roots.root3 = std::complex(std::numeric_limits::infinity(), 0.0); - } else { - double real = -c / (2.0 * b); - double imag = std::sqrt(-discriminant) / (2.0 * b); - roots.root1 = std::complex(real, imag); - roots.root2 = std::complex(real, -imag); - roots.root3 = std::complex(std::numeric_limits::infinity(), 0.0); - } - return roots; - } - - // Handle special case when d is zero - one root is zero - if (std::abs(d) < epsilon) { - // One root is exactly zero - roots.root1 = std::complex(0.0, 0.0); - - // Solve the quadratic: az^2 + bz + c = 0 - double quadDiscriminant = b * b - 4.0 * a * c; - if (quadDiscriminant >= 0) { - double sqrtDiscriminant = std::sqrt(quadDiscriminant); - double r1 = (-b + sqrtDiscriminant) / (2.0 * a); - double r2 = (-b - sqrtDiscriminant) / (2.0 * a); - - // Ensure one positive and one negative root - if (r1 > 0 && r2 > 0) { - // Both positive, make one negative - roots.root2 = std::complex(r1, 0.0); - roots.root3 = std::complex(-std::abs(r2), 0.0); - } else if (r1 < 0 && r2 < 0) { - // Both negative, make one positive - roots.root2 = std::complex(-std::abs(r1), 0.0); - roots.root3 = std::complex(std::abs(r2), 0.0); - } else { - // Already have one positive and one negative - roots.root2 = std::complex(r1, 0.0); - roots.root3 = std::complex(r2, 0.0); - } - } else { - double real = -b / (2.0 * a); - double imag = std::sqrt(-quadDiscriminant) / (2.0 * a); - roots.root2 = std::complex(real, imag); - roots.root3 = std::complex(real, -imag); - } - return roots; - } - - // Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0 - double p = b / a; - double q = c / a; - double r = d / a; - - // Scale coefficients to improve numerical stability - double scale = 1.0; - double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)}); - if (maxCoeff > 1.0) { - scale = 1.0 / maxCoeff; - p *= scale; - q *= scale * scale; - r *= scale * scale * scale; - } - - // Calculate the discriminant for the cubic equation - double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r; - - // Apply a depression transformation: z = t - p/3 - // This gives t^3 + pt + q = 0 (depressed cubic) - double p1 = q - p * p / 3.0; - double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0; - - // The depression shift - double shift = p / 3.0; - - // Cardano's formula parameters - double delta0 = p1; - double delta1 = q1; - - // For tracking if we need to force the pattern - bool forcePattern = false; - - // Check if discriminant is close to zero (multiple roots) - if (std::abs(discriminant) < zero_threshold) { - forcePattern = true; - - if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) { - // Triple root case - roots.root1 = std::complex(-shift, 0.0); - roots.root2 = std::complex(-shift, 0.0); - roots.root3 = std::complex(-shift, 0.0); - return roots; - } - - if (std::abs(delta0) < zero_threshold) { - // Delta0 β‰ˆ 0: One double root and one simple root - double simple = std::cbrt(-delta1); - double doubleRoot = -simple/2 - shift; - double simpleRoot = simple - shift; - - // Force pattern - one zero, one positive, one negative - roots.root1 = std::complex(0.0, 0.0); - - if (doubleRoot > 0) { - roots.root2 = std::complex(doubleRoot, 0.0); - roots.root3 = std::complex(-std::abs(simpleRoot), 0.0); - } else { - roots.root2 = std::complex(-std::abs(doubleRoot), 0.0); - roots.root3 = std::complex(std::abs(simpleRoot), 0.0); - } - return roots; - } - - // One simple root and one double root - double simple = delta1 / delta0; - double doubleRoot = -delta0/3 - shift; - double simpleRoot = simple - shift; - - // Force pattern - one zero, one positive, one negative - roots.root1 = std::complex(0.0, 0.0); - - if (doubleRoot > 0) { - roots.root2 = std::complex(doubleRoot, 0.0); - roots.root3 = std::complex(-std::abs(simpleRoot), 0.0); - } else { - roots.root2 = std::complex(-std::abs(doubleRoot), 0.0); - roots.root3 = std::complex(std::abs(simpleRoot), 0.0); - } - return roots; - } - - // Handle case with three real roots (discriminant > 0) - if (discriminant > 0) { - // Using trigonometric solution for three real roots - double A = std::sqrt(-4.0 * p1 / 3.0); - double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0; - - double root1 = A * std::cos(B) - shift; - double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift; - double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift; - - // Check for roots close to zero - if (std::abs(root1) < zero_threshold) root1 = 0.0; - if (std::abs(root2) < zero_threshold) root2 = 0.0; - if (std::abs(root3) < zero_threshold) root3 = 0.0; - - // Check if we already have the desired pattern - int zeros = 0, positives = 0, negatives = 0; - if (root1 == 0.0) zeros++; - else if (root1 > 0) positives++; - else negatives++; - - if (root2 == 0.0) zeros++; - else if (root2 > 0) positives++; - else negatives++; - - if (root3 == 0.0) zeros++; - else if (root3 > 0) positives++; - else negatives++; - - // If we don't have the pattern, force it - if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) { - forcePattern = true; - // Sort roots to make manipulation easier - std::vector sorted_roots = {root1, root2, root3}; - std::sort(sorted_roots.begin(), sorted_roots.end()); - - // Force pattern: one zero, one positive, one negative - roots.root1 = std::complex(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative - roots.root2 = std::complex(0.0, 0.0); // Set middle to zero - roots.root3 = std::complex(std::abs(sorted_roots[2]), 0.0); // Make the largest positive - return roots; - } - - // We have the right pattern, assign the roots - roots.root1 = std::complex(root1, 0.0); - roots.root2 = std::complex(root2, 0.0); - roots.root3 = std::complex(root3, 0.0); - return roots; - } - - // One real root and two complex conjugate roots - double C, D; - if (q1 >= 0) { - C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0); - } else { - C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0); - } - - if (std::abs(C) < epsilon) { - D = 0; - } else { - D = -p1 / (3.0 * C); - } - - // The real root - double realRoot = C + D - shift; - - // The two complex conjugate roots - double realPart = -(C + D) / 2.0 - shift; - double imagPart = std::sqrt(3.0) * (C - D) / 2.0; - - // Check if real root is close to zero - if (std::abs(realRoot) < zero_threshold) { - // Already have one zero root - roots.root1 = std::complex(0.0, 0.0); - roots.root2 = std::complex(realPart, imagPart); - roots.root3 = std::complex(realPart, -imagPart); - } else { - // Force the desired pattern - one zero, one positive, one negative - if (forcePattern) { - roots.root1 = std::complex(0.0, 0.0); // Force one root to be zero - if (realRoot > 0) { - // Real root is positive, make complex part negative - roots.root2 = std::complex(realRoot, 0.0); - roots.root3 = std::complex(-std::abs(realPart), 0.0); - } else { - // Real root is negative, need a positive root - roots.root2 = std::complex(-realRoot, 0.0); // Force to positive - roots.root3 = std::complex(realRoot, 0.0); // Keep original negative - } - } else { - // Standard assignment - roots.root1 = std::complex(realRoot, 0.0); - roots.root2 = std::complex(realPart, imagPart); - roots.root3 = std::complex(realPart, -imagPart); - } - } - - return roots; -} - -// Function to compute the theoretical max value -double compute_theoretical_max(double a, double y, double beta, int grid_points, double tolerance) { - auto f = [a, y, beta](double k) -> double { - return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) / - ((a * k + 1) * (k * k + k)); - }; - - // Use numerical optimization to find the maximum - // Grid search followed by golden section search - double best_k = 1.0; - double best_val = f(best_k); - - // Initial grid search over a wide range - const int num_grid_points = grid_points; - for (int i = 0; i < num_grid_points; ++i) { - double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100 - double val = f(k); - if (val > best_val) { - best_val = val; - best_k = k; - } - } - - // Refine with golden section search - double a_gs = std::max(0.01, best_k / 10.0); - double b_gs = best_k * 10.0; - const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0; - - double c_gs = b_gs - (b_gs - a_gs) / golden_ratio; - double d_gs = a_gs + (b_gs - a_gs) / golden_ratio; - - while (std::abs(b_gs - a_gs) > tolerance) { - if (f(c_gs) > f(d_gs)) { - b_gs = d_gs; - d_gs = c_gs; - c_gs = b_gs - (b_gs - a_gs) / golden_ratio; - } else { - a_gs = c_gs; - c_gs = d_gs; - d_gs = a_gs + (b_gs - a_gs) / golden_ratio; - } - } - - // Return the value without multiplying by y (as per correction) - return f((a_gs + b_gs) / 2.0); -} - -// Function to compute the theoretical min value -double compute_theoretical_min(double a, double y, double beta, int grid_points, double tolerance) { - auto f = [a, y, beta](double t) -> double { - return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) / - ((a * t + 1) * (t * t + t)); - }; - - // Use numerical optimization to find the minimum - // Grid search followed by golden section search - double best_t = -0.5 / a; // Midpoint of (-1/a, 0) - double best_val = f(best_t); - - // Initial grid search over the range (-1/a, 0) - const int num_grid_points = grid_points; - for (int i = 1; i < num_grid_points; ++i) { - // From slightly above -1/a to slightly below 0 - double t = -0.999/a + 0.998/a * i / (num_grid_points - 1); - if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0) - - double val = f(t); - if (val < best_val) { - best_val = val; - best_t = t; - } - } - - // Refine with golden section search - double a_gs = -0.999/a; // Slightly above -1/a - double b_gs = -0.001/a; // Slightly below 0 - const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0; - - double c_gs = b_gs - (b_gs - a_gs) / golden_ratio; - double d_gs = a_gs + (b_gs - a_gs) / golden_ratio; - - while (std::abs(b_gs - a_gs) > tolerance) { - if (f(c_gs) < f(d_gs)) { - b_gs = d_gs; - d_gs = c_gs; - c_gs = b_gs - (b_gs - a_gs) / golden_ratio; - } else { - a_gs = c_gs; - c_gs = d_gs; - d_gs = a_gs + (b_gs - a_gs) / golden_ratio; - } - } - - // Return the value without multiplying by y (as per correction) - return f((a_gs + b_gs) / 2.0); -} - -// Function to save data as JSON -bool save_as_json(const std::string& filename, - const std::vector& beta_values, - const std::vector& max_eigenvalues, - const std::vector& min_eigenvalues, - const std::vector& theoretical_max_values, - const std::vector& theoretical_min_values) { - - std::ofstream outfile(filename); - - if (!outfile.is_open()) { - std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl; - return false; - } - - // Helper function to format floating point values safely for JSON - auto formatJsonValue = [](double value) -> std::string { - if (std::isnan(value)) { - return "\"NaN\""; // JSON doesn't support NaN, so use string - } else if (std::isinf(value)) { - if (value > 0) { - return "\"Infinity\""; // JSON doesn't support Infinity, so use string - } else { - return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string - } - } else { - // Use a fixed precision to avoid excessively long numbers - std::ostringstream oss; - oss << std::setprecision(15) << value; - return oss.str(); - } - }; - - // Start JSON object - outfile << "{\n"; - - // Write beta values - outfile << " \"beta_values\": ["; - for (size_t i = 0; i < beta_values.size(); ++i) { - outfile << formatJsonValue(beta_values[i]); - if (i < beta_values.size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write max eigenvalues - outfile << " \"max_eigenvalues\": ["; - for (size_t i = 0; i < max_eigenvalues.size(); ++i) { - outfile << formatJsonValue(max_eigenvalues[i]); - if (i < max_eigenvalues.size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write min eigenvalues - outfile << " \"min_eigenvalues\": ["; - for (size_t i = 0; i < min_eigenvalues.size(); ++i) { - outfile << formatJsonValue(min_eigenvalues[i]); - if (i < min_eigenvalues.size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write theoretical max values - outfile << " \"theoretical_max\": ["; - for (size_t i = 0; i < theoretical_max_values.size(); ++i) { - outfile << formatJsonValue(theoretical_max_values[i]); - if (i < theoretical_max_values.size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write theoretical min values - outfile << " \"theoretical_min\": ["; - for (size_t i = 0; i < theoretical_min_values.size(); ++i) { - outfile << formatJsonValue(theoretical_min_values[i]); - if (i < theoretical_min_values.size() - 1) outfile << ", "; - } - outfile << "]\n"; - - // Close JSON object - outfile << "}\n"; - - outfile.close(); - return true; -} - -// Eigenvalue analysis function -bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness, - int theory_grid_points, double theory_tolerance, - const std::string& output_file) { - - std::cout << "Running eigenvalue analysis with parameters: n = " << n << ", p = " << p - << ", a = " << a << ", y = " << y << ", fineness = " << fineness - << ", theory_grid_points = " << theory_grid_points - << ", theory_tolerance = " << theory_tolerance << std::endl; - std::cout << "Output will be saved to: " << output_file << std::endl; - - // ─── Beta range parameters ──────────────────────────────────────── - const int num_beta_points = fineness; // Controlled by fineness parameter - std::vector beta_values(num_beta_points); - for (int i = 0; i < num_beta_points; ++i) { - beta_values[i] = static_cast(i) / (num_beta_points - 1); - } - - // ─── Storage for results ──────────────────────────────────────── - std::vector max_eigenvalues(num_beta_points); - std::vector min_eigenvalues(num_beta_points); - std::vector theoretical_max_values(num_beta_points); - std::vector theoretical_min_values(num_beta_points); - - try { - // ─── Random‐Gaussian X and S_n ──────────────────────────────── - std::random_device rd; - std::mt19937_64 rng{rd()}; - std::normal_distribution norm(0.0, 1.0); - - cv::Mat X(p, n, CV_64F); - for(int i = 0; i < p; ++i) - for(int j = 0; j < n; ++j) - X.at(i,j) = norm(rng); - - // ─── Process each beta value ───────────────────────────────── - for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) { - double beta = beta_values[beta_idx]; - - // Compute theoretical values with customizable precision - theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance); - theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance); - - // ─── Build T_n matrix ────────────────────────────────── - int k = static_cast(std::floor(beta * p)); - std::vector diags(p, 1.0); - std::fill_n(diags.begin(), k, a); - std::shuffle(diags.begin(), diags.end(), rng); - - cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F); - for(int i = 0; i < p; ++i){ - T_n.at(i,i) = diags[i]; - } - - // ─── Form B_n = (1/n) * X * T_n * X^T ──────────── - cv::Mat B = (X.t() * T_n * X) / static_cast(n); - - // ─── Compute eigenvalues of B ──────────────────────────── - cv::Mat eigVals; - cv::eigen(B, eigVals); - std::vector eigs(n); - for(int i = 0; i < n; ++i) - eigs[i] = eigVals.at(i, 0); - - max_eigenvalues[beta_idx] = *std::max_element(eigs.begin(), eigs.end()); - min_eigenvalues[beta_idx] = *std::min_element(eigs.begin(), eigs.end()); - - // Progress indicator for Streamlit - double progress = static_cast(beta_idx + 1) / num_beta_points; - std::cout << "PROGRESS:" << progress << std::endl; - - // Less verbose output for Streamlit - if (beta_idx % 20 == 0 || beta_idx == num_beta_points - 1) { - std::cout << "Processing beta = " << beta - << " (" << beta_idx+1 << "/" << num_beta_points << ")" << std::endl; - } - } - - // Save data as JSON for Python to read - if (!save_as_json(output_file, beta_values, max_eigenvalues, min_eigenvalues, - theoretical_max_values, theoretical_min_values)) { - return false; - } - - std::cout << "Data saved to " << output_file << std::endl; - return true; - } - catch (const std::exception& e) { - std::cerr << "Error in eigenvalue analysis: " << e.what() << std::endl; - return false; - } - catch (...) { - std::cerr << "Unknown error in eigenvalue analysis" << std::endl; - return false; - } -} - -int main(int argc, char* argv[]) { - // Print received arguments for debugging - std::cout << "Received " << argc << " arguments:" << std::endl; - for (int i = 0; i < argc; ++i) { - std::cout << " argv[" << i << "]: " << argv[i] << std::endl; - } - - // Check for mode argument - if (argc < 2) { - std::cerr << "Error: Missing mode argument." << std::endl; - std::cerr << "Usage: " << argv[0] << " eigenvalues

" << std::endl; - return 1; - } - - std::string mode = argv[1]; - - try { - if (mode == "eigenvalues") { - // ─── Eigenvalue analysis mode ─────────────────────────────────────────── - if (argc != 10) { - std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl; - std::cerr << "Usage: " << argv[0] << " eigenvalues

" << std::endl; - std::cerr << "Received " << argc << " arguments, expected 10." << std::endl; - return 1; - } - - int n = std::stoi(argv[2]); - int p = std::stoi(argv[3]); - double a = std::stod(argv[4]); - double y = std::stod(argv[5]); - int fineness = std::stoi(argv[6]); - int theory_grid_points = std::stoi(argv[7]); - double theory_tolerance = std::stod(argv[8]); - std::string output_file = argv[9]; - - if (!eigenvalueAnalysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance, output_file)) { - return 1; - } - } else { - std::cerr << "Error: Unknown mode: " << mode << std::endl; - std::cerr << "Use 'eigenvalues'" << std::endl; - return 1; - } - } - catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; - } - - return 0; -} - ''') - -# Compile the C++ code with the right OpenCV libraries -st.sidebar.title("Dashboard Settings") -need_compile = not os.path.exists(executable) or st.sidebar.button("Recompile C++ Code") - -if need_compile: - with st.sidebar: - with st.spinner("Compiling C++ code..."): - # Try to detect the OpenCV installation - opencv_detection_cmd = ["pkg-config", "--cflags", "--libs", "opencv4"] - opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False) - - compile_commands = [] - - if opencv_found: - compile_commands.append( - f"g++ -o {executable} {cpp_file} {opencv_flags.strip()} -std=c++11" - ) - else: - # Try different OpenCV configurations - compile_commands = [ - f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv4` -std=c++11", - f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv` -std=c++11", - f"g++ -o {executable} {cpp_file} -I/usr/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11", - f"g++ -o {executable} {cpp_file} -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11" - ] - - compiled = False - compile_output = "" - - for cmd in compile_commands: - st.text(f"Trying: {cmd}") - success, stdout, stderr = run_command(cmd.split(), show_output=False) - compile_output += f"Command: {cmd}\nOutput: {stdout}\nError: {stderr}\n\n" - - if success: - compiled = True - st.success(f"Successfully compiled with: {cmd}") - break - - if not compiled: - st.error("All compilation attempts failed.") - with st.expander("Compilation Details"): - st.code(compile_output) - st.stop() - - # Make sure the executable is executable - if platform.system() != "Windows": - os.chmod(executable, 0o755) - - st.success("C++ code compiled successfully!") + st.error(f"C++ source file not found at: {cpp_file}") + st.info("Please ensure app.cpp is in the current directory.") + st.stop() # Set higher precision for mpmath mpmath.mp.dps = 100 # 100 digits of precision @@ -1044,7 +378,7 @@ def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max, progress_callback=Non progress_callback(i / num_points) # Coefficients for the cubic equation: - # zasΒ³ + [z(a+1)+a(1-y)]sΒ² + [z+(a+1)-y-yΞ²(a-1)]s + 1 = 0 + # zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0 coef_a = z * a coef_b = z * (a + 1) + a * (1 - y) coef_c = z + (a + 1) - y - y * beta * (a - 1) @@ -1813,6 +1147,58 @@ def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42): # Options for theme and appearance def compute_eigenvalue_support_boundaries(z_a, y, betas, n_samples=1000, seeds=5): return np.zeros(len(betas)), np.ones(len(betas)) + +# Compile the C++ code with the right OpenCV libraries +st.sidebar.title("Dashboard Settings") +need_compile = not os.path.exists(executable) or st.sidebar.button("Recompile C++ Code") + +if need_compile: + with st.sidebar: + with st.spinner("Compiling C++ code..."): + # Try to detect the OpenCV installation + opencv_detection_cmd = ["pkg-config", "--cflags", "--libs", "opencv4"] + opencv_found, opencv_flags, _ = run_command(opencv_detection_cmd, show_output=False) + + compile_commands = [] + + if opencv_found: + compile_commands.append( + f"g++ -o {executable} {cpp_file} {opencv_flags.strip()} -std=c++11" + ) + else: + # Try different OpenCV configurations + compile_commands = [ + f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv4` -std=c++11", + f"g++ -o {executable} {cpp_file} `pkg-config --cflags --libs opencv` -std=c++11", + f"g++ -o {executable} {cpp_file} -I/usr/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11", + f"g++ -o {executable} {cpp_file} -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgproc -std=c++11" + ] + + compiled = False + compile_output = "" + + for cmd in compile_commands: + st.text(f"Trying: {cmd}") + success, stdout, stderr = run_command(cmd.split(), show_output=False) + compile_output += f"Command: {cmd}\nOutput: {stdout}\nError: {stderr}\n\n" + + if success: + compiled = True + st.success(f"Successfully compiled with: {cmd}") + break + + if not compiled: + st.error("All compilation attempts failed.") + with st.expander("Compilation Details"): + st.code(compile_output) + st.stop() + + # Make sure the executable is executable + if platform.system() != "Windows": + os.chmod(executable, 0o755) + + st.success("C++ code compiled successfully!") + with st.sidebar.expander("Theme & Appearance"): show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots") color_theme = st.selectbox( @@ -1851,470 +1237,972 @@ with st.sidebar.expander("Theme & Appearance"): # Create tabs for different analyses tab1, tab2 = st.tabs(["Eigenvalue Analysis (C++)", "Im(s) vs z Analysis (SymPy)"]) -# Tab 1: Eigenvalue Analysis (KEEP UNCHANGED from original) +# Tab 1: Eigenvalue Analysis with sub-tabs with tab1: - # Two-column layout for the dashboard - left_column, right_column = st.columns([1, 3]) + # Create sub-tabs for different eigenvalue analyses + eig_subtabs = st.tabs(["Varying Ξ² Analysis", "Fixed Ξ² Analysis"]) - with left_column: - st.markdown('

', unsafe_allow_html=True) - st.markdown('
Eigenvalue Analysis Controls
', unsafe_allow_html=True) - - # Parameter inputs with defaults and validation - st.markdown('
', unsafe_allow_html=True) - st.markdown("### Matrix Parameters") - n = st.number_input("Sample size (n)", min_value=5, max_value=10000000, value=100, step=5, - help="Number of samples", key="eig_n") - p = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5, - help="Dimensionality", key="eig_p") - a = st.number_input("Value for a", min_value=1.1, max_value=10000.0, value=2.0, step=0.1, - help="Parameter a > 1", key="eig_a") - - # Automatically calculate y = p/n (as requested) - y = p/n - st.info(f"Value for y = p/n: {y:.4f}") - st.markdown('
', unsafe_allow_html=True) + # Sub-tab 1: Varying Beta Analysis (original functionality) + with eig_subtabs[0]: + # Two-column layout for the dashboard + left_column, right_column = st.columns([1, 3]) - st.markdown('
', unsafe_allow_html=True) - st.markdown("### Calculation Controls") - fineness = st.slider( - "Beta points", - min_value=20, - max_value=500, - value=100, - step=10, - help="Number of points to calculate along the Ξ² axis (0 to 1)", - key="eig_fineness" - ) - st.markdown('
', unsafe_allow_html=True) - - with st.expander("Advanced Settings"): - # Add controls for theoretical calculation precision - theory_grid_points = st.slider( - "Theoretical grid points", - min_value=100, - max_value=1000, - value=200, - step=50, - help="Number of points in initial grid search for theoretical calculations", - key="eig_grid_points" - ) + with left_column: + st.markdown('
', unsafe_allow_html=True) + st.markdown('
Varying Ξ² Analysis Controls
', unsafe_allow_html=True) - theory_tolerance = st.number_input( - "Theoretical tolerance", - min_value=1e-12, - max_value=1e-6, - value=1e-10, - format="%.1e", - help="Convergence tolerance for golden section search", - key="eig_tolerance" - ) + # Parameter inputs with defaults and validation + st.markdown('
', unsafe_allow_html=True) + st.markdown("### Matrix Parameters") + n = st.number_input("Sample size (n)", min_value=5, max_value=10000000, value=100, step=5, + help="Number of samples", key="eig_n") + p = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5, + help="Dimensionality", key="eig_p") + a = st.number_input("Value for a", min_value=1.1, max_value=10000.0, value=2.0, step=0.1, + help="Parameter a > 1", key="eig_a") - # Debug mode - debug_mode = st.checkbox("Debug Mode", value=False, key="eig_debug") + # Automatically calculate y = p/n (as requested) + y = p/n + st.info(f"Value for y = p/n: {y:.4f}") + st.markdown('
', unsafe_allow_html=True) - # Timeout setting - timeout_seconds = st.number_input( - "Computation timeout (seconds)", - min_value=30, - max_value=3600, - value=300, - help="Maximum time allowed for computation before timeout", - key="eig_timeout" + st.markdown('
', unsafe_allow_html=True) + st.markdown("### Calculation Controls") + fineness = st.slider( + "Beta points", + min_value=20, + max_value=500, + value=100, + step=10, + help="Number of points to calculate along the Ξ² axis (0 to 1)", + key="eig_fineness" ) - - # Generate button - eig_generate_button = st.button("Generate Eigenvalue Analysis", - type="primary", - use_container_width=True, - key="eig_generate") - st.markdown('
', unsafe_allow_html=True) - - with right_column: - # Main visualization area - st.markdown('
', unsafe_allow_html=True) - st.markdown('
Eigenvalue Analysis Results
', unsafe_allow_html=True) - - # Container for the analysis results - eig_results_container = st.container() - - # Process when generate button is clicked - if eig_generate_button: - with eig_results_container: - # Show progress - progress_container = st.container() - with progress_container: - progress_bar = st.progress(0) - status_text = st.empty() + st.markdown('
', unsafe_allow_html=True) + + with st.expander("Advanced Settings"): + # Add controls for theoretical calculation precision + theory_grid_points = st.slider( + "Theoretical grid points", + min_value=100, + max_value=1000, + value=200, + step=50, + help="Number of points in initial grid search for theoretical calculations", + key="eig_grid_points" + ) - try: - # Create data file path - data_file = os.path.join(output_dir, "eigenvalue_data.json") - - # Delete previous output if exists - if os.path.exists(data_file): - os.remove(data_file) - - # Build command for eigenvalue analysis with the proper arguments - cmd = [ - executable, - "eigenvalues", # Mode argument - str(n), - str(p), - str(a), - str(y), - str(fineness), - str(theory_grid_points), - str(theory_tolerance), - data_file - ] - - # Run the command - status_text.text("Running eigenvalue analysis...") + theory_tolerance = st.number_input( + "Theoretical tolerance", + min_value=1e-12, + max_value=1e-6, + value=1e-10, + format="%.1e", + help="Convergence tolerance for golden section search", + key="eig_tolerance" + ) + + # Debug mode + debug_mode = st.checkbox("Debug Mode", value=False, key="eig_debug") + + # Timeout setting + timeout_seconds = st.number_input( + "Computation timeout (seconds)", + min_value=30, + max_value=3600, + value=300, + help="Maximum time allowed for computation before timeout", + key="eig_timeout" + ) + + # Generate button + eig_generate_button = st.button("Generate Varying Ξ² Analysis", + type="primary", + use_container_width=True, + key="eig_generate") + st.markdown('
', unsafe_allow_html=True) + + with right_column: + # Main visualization area + st.markdown('
', unsafe_allow_html=True) + st.markdown('
Varying Ξ² Analysis Results
', unsafe_allow_html=True) + + # Container for the analysis results + eig_results_container = st.container() + + # Process when generate button is clicked (existing logic) + if eig_generate_button: + with eig_results_container: + # Show progress + progress_container = st.container() + with progress_container: + progress_bar = st.progress(0) + status_text = st.empty() - if debug_mode: - success, stdout, stderr = run_command(cmd, True, timeout=timeout_seconds) - # Process stdout for progress updates - if success: - progress_bar.progress(1.0) - else: - # Start the process with pipe for stdout to read progress - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1, - universal_newlines=True - ) + try: + # Create data file path + data_file = os.path.join(output_dir, "eigenvalue_data.json") + + # Delete previous output if exists + if os.path.exists(data_file): + os.remove(data_file) + + # Build command for eigenvalue analysis with the proper arguments + cmd = [ + executable, + "eigenvalues", # Mode argument + str(n), + str(p), + str(a), + str(y), + str(fineness), + str(theory_grid_points), + str(theory_tolerance), + data_file + ] - # Track progress from stdout - success = True - stdout_lines = [] + # Run the command + status_text.text("Running eigenvalue analysis...") - start_time = time.time() - while True: - # Check for timeout - if time.time() - start_time > timeout_seconds: - process.kill() - status_text.error(f"Computation timed out after {timeout_seconds} seconds") + if debug_mode: + success, stdout, stderr = run_command(cmd, True, timeout=timeout_seconds) + # Process stdout for progress updates + if success: + progress_bar.progress(1.0) + else: + # Start the process with pipe for stdout to read progress + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Track progress from stdout + success = True + stdout_lines = [] + + start_time = time.time() + while True: + # Check for timeout + if time.time() - start_time > timeout_seconds: + process.kill() + status_text.error(f"Computation timed out after {timeout_seconds} seconds") + success = False + break + + # Try to read a line (non-blocking) + line = process.stdout.readline() + if not line and process.poll() is not None: + break + + if line: + stdout_lines.append(line) + if line.startswith("PROGRESS:"): + try: + # Update progress bar + progress_value = float(line.split(":")[1].strip()) + progress_bar.progress(progress_value) + status_text.text(f"Calculating... {int(progress_value * 100)}% complete") + except: + pass + elif line: + status_text.text(line.strip()) + + # Get the return code and stderr + returncode = process.poll() + stderr = process.stderr.read() + + if returncode != 0: success = False - break + st.error(f"Error executing the analysis: {stderr}") + with st.expander("Error Details"): + st.code(stderr) + + if success: + progress_bar.progress(1.0) + status_text.text("Analysis complete! Generating visualization...") + + # Check if the output file was created + if not os.path.exists(data_file): + st.error(f"Output file not created: {data_file}") + st.stop() + + try: + # Load the results from the JSON file + with open(data_file, 'r') as f: + data = json.load(f) + + # Process data - convert string values to numeric + beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']]) + max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) + min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) + theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) + theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) + + # Create an interactive plot using Plotly + fig = go.Figure() + + # Add traces for each line + fig.add_trace(go.Scatter( + x=beta_values, + y=max_eigenvalues, + mode='lines+markers', + name='Empirical Max Eigenvalue', + line=dict(color=color_max, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_max, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Max' + )) + + fig.add_trace(go.Scatter( + x=beta_values, + y=min_eigenvalues, + mode='lines+markers', + name='Empirical Min Eigenvalue', + line=dict(color=color_min, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_min, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Min' + )) - # Try to read a line (non-blocking) - line = process.stdout.readline() - if not line and process.poll() is not None: - break + fig.add_trace(go.Scatter( + x=beta_values, + y=theoretical_max, + mode='lines+markers', + name='Theoretical Max', + line=dict(color=color_theory_max, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_max, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Max' + )) - if line: - stdout_lines.append(line) - if line.startswith("PROGRESS:"): - try: - # Update progress bar - progress_value = float(line.split(":")[1].strip()) - progress_bar.progress(progress_value) - status_text.text(f"Calculating... {int(progress_value * 100)}% complete") - except: - pass - elif line: - status_text.text(line.strip()) + fig.add_trace(go.Scatter( + x=beta_values, + y=theoretical_min, + mode='lines+markers', + name='Theoretical Min', + line=dict(color=color_theory_min, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_min, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Min' + )) + + # Configure layout for better appearance + fig.update_layout( + title={ + 'text': f'Varying Ξ² Analysis: n={n}, p={p}, a={a}, y={y:.4f}', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + yaxis={ + 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + plot_bgcolor='rgba(250, 250, 250, 0.8)', + paper_bgcolor='rgba(255, 255, 255, 0.8)', + hovermode='closest', + legend={ + 'font': {'size': 14}, + 'bgcolor': 'rgba(255, 255, 255, 0.9)', + 'bordercolor': 'rgba(200, 200, 200, 0.5)', + 'borderwidth': 1 + }, + margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, + height=600, + ) + + # Add custom modebar buttons + fig.update_layout( + modebar_add=[ + 'drawline', 'drawopenpath', 'drawclosedpath', + 'drawcircle', 'drawrect', 'eraseshape' + ], + modebar_remove=['lasso2d', 'select2d'], + dragmode='zoom' + ) + + # Clear progress container + progress_container.empty() + + # Display the interactive plot in Streamlit + st.plotly_chart(fig, use_container_width=True) + + # Display statistics in a cleaner way + st.markdown('
', unsafe_allow_html=True) + col1, col2, col3, col4 = st.columns(4) + with col1: + st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}") + with col2: + st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}") + with col3: + st.metric("Max Theoretical", f"{theoretical_max.max():.4f}") + with col4: + st.metric("Min Theoretical", f"{theoretical_min.min():.4f}") + st.markdown('
', unsafe_allow_html=True) - # Get the return code and stderr - returncode = process.poll() - stderr = process.stderr.read() - - if returncode != 0: - success = False - st.error(f"Error executing the analysis: {stderr}") - with st.expander("Error Details"): - st.code(stderr) + except json.JSONDecodeError as e: + st.error(f"Error parsing JSON results: {str(e)}") + if os.path.exists(data_file): + with open(data_file, 'r') as f: + content = f.read() + st.code(content[:1000] + "..." if len(content) > 1000 else content) - if success: - progress_bar.progress(1.0) - status_text.text("Analysis complete! Generating visualization...") + except Exception as e: + st.error(f"An error occurred: {str(e)}") + if debug_mode: + st.exception(e) + + else: + # Try to load existing data if available + data_file = os.path.join(output_dir, "eigenvalue_data.json") + if os.path.exists(data_file): + try: + with open(data_file, 'r') as f: + data = json.load(f) + + # Process data - convert string values to numeric + beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']]) + max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) + min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) + theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) + theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) + + # Create an interactive plot using Plotly + fig = go.Figure() - # Check if the output file was created - if not os.path.exists(data_file): - st.error(f"Output file not created: {data_file}") - st.stop() + # Add traces for each line + fig.add_trace(go.Scatter( + x=beta_values, + y=max_eigenvalues, + mode='lines+markers', + name='Empirical Max Eigenvalue', + line=dict(color=color_max, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_max, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Max' + )) + + fig.add_trace(go.Scatter( + x=beta_values, + y=min_eigenvalues, + mode='lines+markers', + name='Empirical Min Eigenvalue', + line=dict(color=color_min, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_min, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Min' + )) + + fig.add_trace(go.Scatter( + x=beta_values, + y=theoretical_max, + mode='lines+markers', + name='Theoretical Max', + line=dict(color=color_theory_max, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_max, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Max' + )) + + fig.add_trace(go.Scatter( + x=beta_values, + y=theoretical_min, + mode='lines+markers', + name='Theoretical Min', + line=dict(color=color_theory_min, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_min, + line=dict(color='white', width=1) + ), + hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Min' + )) + + # Configure layout for better appearance + fig.update_layout( + title={ + 'text': f'Varying Ξ² Analysis (Previous Result)', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + yaxis={ + 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + plot_bgcolor='rgba(250, 250, 250, 0.8)', + paper_bgcolor='rgba(255, 255, 255, 0.8)', + hovermode='closest', + legend={ + 'font': {'size': 14}, + 'bgcolor': 'rgba(255, 255, 255, 0.9)', + 'bordercolor': 'rgba(200, 200, 200, 0.5)', + 'borderwidth': 1 + }, + margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, + height=600 + ) + + # Display the interactive plot in Streamlit + st.plotly_chart(fig, use_container_width=True) + st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.") + + except Exception as e: + st.info("Set parameters and click 'Generate Varying Ξ² Analysis' to create a visualization.") + else: + # Show placeholder + st.info("Set parameters and click 'Generate Varying Ξ² Analysis' to create a visualization.") + + st.markdown('
', unsafe_allow_html=True) + + # Sub-tab 2: Fixed Beta Analysis (new functionality) + with eig_subtabs[1]: + # Two-column layout for the dashboard + left_column_fixed, right_column_fixed = st.columns([1, 3]) + + with left_column_fixed: + st.markdown('
', unsafe_allow_html=True) + st.markdown('
Fixed Ξ² Analysis Controls
', unsafe_allow_html=True) + + # Parameter inputs with defaults and validation + st.markdown('
', unsafe_allow_html=True) + st.markdown("### Matrix Parameters") + n_fixed = st.number_input("Sample size (n)", min_value=5, max_value=10000000, value=100, step=5, + help="Number of samples", key="eig_n_fixed") + p_fixed = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5, + help="Dimensionality", key="eig_p_fixed") + + # Automatically calculate y = p/n + y_fixed = p_fixed/n_fixed + st.info(f"Value for y = p/n: {y_fixed:.4f}") + + # Fixed beta parameter + beta_fixed = st.slider("Fixed Ξ² value", min_value=0.0, max_value=1.0, value=0.5, step=0.01, + help="Fixed value of Ξ² parameter", key="eig_beta_fixed") + st.markdown('
', unsafe_allow_html=True) + + st.markdown('
', unsafe_allow_html=True) + st.markdown("### Parameter Range") + a_min = st.number_input("Minimum a value", min_value=1.1, max_value=100.0, value=1.1, step=0.1, + help="Minimum value for parameter a", key="eig_a_min") + a_max = st.number_input("Maximum a value", min_value=1.1, max_value=100.0, value=5.0, step=0.1, + help="Maximum value for parameter a", key="eig_a_max") + + if a_min >= a_max: + st.error("Minimum a value must be less than maximum a value") + + fineness_fixed = st.slider( + "Parameter points", + min_value=20, + max_value=500, + value=100, + step=10, + help="Number of points to calculate along the a axis", + key="eig_fineness_fixed" + ) + st.markdown('
', unsafe_allow_html=True) + + with st.expander("Advanced Settings"): + # Add controls for theoretical calculation precision + theory_grid_points_fixed = st.slider( + "Theoretical grid points", + min_value=100, + max_value=1000, + value=200, + step=50, + help="Number of points in initial grid search for theoretical calculations", + key="eig_grid_points_fixed" + ) + + theory_tolerance_fixed = st.number_input( + "Theoretical tolerance", + min_value=1e-12, + max_value=1e-6, + value=1e-10, + format="%.1e", + help="Convergence tolerance for golden section search", + key="eig_tolerance_fixed" + ) + + # Debug mode + debug_mode_fixed = st.checkbox("Debug Mode", value=False, key="eig_debug_fixed") + + # Timeout setting + timeout_seconds_fixed = st.number_input( + "Computation timeout (seconds)", + min_value=30, + max_value=3600, + value=300, + help="Maximum time allowed for computation before timeout", + key="eig_timeout_fixed" + ) + + # Generate button + eig_generate_button_fixed = st.button("Generate Fixed Ξ² Analysis", + type="primary", + use_container_width=True, + key="eig_generate_fixed") + st.markdown('
', unsafe_allow_html=True) + + with right_column_fixed: + # Main visualization area + st.markdown('
', unsafe_allow_html=True) + st.markdown('
Fixed Ξ² Analysis Results
', unsafe_allow_html=True) + + # Container for the analysis results + eig_results_container_fixed = st.container() + + # Process when generate button is clicked + if eig_generate_button_fixed: + if a_min >= a_max: + st.error("Please ensure minimum a value is less than maximum a value") + else: + with eig_results_container_fixed: + # Show progress + progress_container_fixed = st.container() + with progress_container_fixed: + progress_bar_fixed = st.progress(0) + status_text_fixed = st.empty() try: - # Load the results from the JSON file - with open(data_file, 'r') as f: - data = json.load(f) - - # Process data - convert string values to numeric - beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']]) - max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) - min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) - theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) - theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) - - # Create an interactive plot using Plotly - fig = go.Figure() + # Create data file path + data_file_fixed = os.path.join(output_dir, "eigenvalue_fixed_beta_data.json") - # Add traces for each line - fig.add_trace(go.Scatter( - x=beta_values, - y=max_eigenvalues, - mode='lines+markers', - name='Empirical Max Eigenvalue', - line=dict(color=color_max, width=3), - marker=dict( - symbol='circle', - size=8, - color=color_max, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Max' - )) + # Delete previous output if exists + if os.path.exists(data_file_fixed): + os.remove(data_file_fixed) - fig.add_trace(go.Scatter( - x=beta_values, - y=min_eigenvalues, - mode='lines+markers', - name='Empirical Min Eigenvalue', - line=dict(color=color_min, width=3), - marker=dict( - symbol='circle', - size=8, - color=color_min, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Min' - )) + # Build command for fixed beta eigenvalue analysis + cmd_fixed = [ + executable, + "eigenvalues_fixed_beta", # Mode argument + str(n_fixed), + str(p_fixed), + str(y_fixed), + str(beta_fixed), + str(a_min), + str(a_max), + str(fineness_fixed), + str(theory_grid_points_fixed), + str(theory_tolerance_fixed), + data_file_fixed + ] - fig.add_trace(go.Scatter( - x=beta_values, - y=theoretical_max, - mode='lines+markers', - name='Theoretical Max', - line=dict(color=color_theory_max, width=3), - marker=dict( - symbol='diamond', - size=8, - color=color_theory_max, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Max' - )) + # Run the command + status_text_fixed.text("Running fixed Ξ² eigenvalue analysis...") - fig.add_trace(go.Scatter( - x=beta_values, - y=theoretical_min, - mode='lines+markers', - name='Theoretical Min', - line=dict(color=color_theory_min, width=3), - marker=dict( - symbol='diamond', - size=8, - color=color_theory_min, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Min' - )) - - # Configure layout for better appearance - fig.update_layout( - title={ - 'text': f'Eigenvalue Analysis: n={n}, p={p}, a={a}, y={y:.4f}', - 'font': {'size': 24, 'color': '#0e1117'}, - 'y': 0.95, - 'x': 0.5, - 'xanchor': 'center', - 'yanchor': 'top' - }, - xaxis={ - 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True - }, - yaxis={ - 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True - }, - plot_bgcolor='rgba(250, 250, 250, 0.8)', - paper_bgcolor='rgba(255, 255, 255, 0.8)', - hovermode='closest', - legend={ - 'font': {'size': 14}, - 'bgcolor': 'rgba(255, 255, 255, 0.9)', - 'bordercolor': 'rgba(200, 200, 200, 0.5)', - 'borderwidth': 1 - }, - margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, - height=600, - ) - - # Add custom modebar buttons - fig.update_layout( - modebar_add=[ - 'drawline', 'drawopenpath', 'drawclosedpath', - 'drawcircle', 'drawrect', 'eraseshape' - ], - modebar_remove=['lasso2d', 'select2d'], - dragmode='zoom' - ) - - # Clear progress container - progress_container.empty() - - # Display the interactive plot in Streamlit - st.plotly_chart(fig, use_container_width=True) + if debug_mode_fixed: + success, stdout, stderr = run_command(cmd_fixed, True, timeout=timeout_seconds_fixed) + if success: + progress_bar_fixed.progress(1.0) + else: + # Start the process with pipe for stdout to read progress + process = subprocess.Popen( + cmd_fixed, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Track progress from stdout + success = True + stdout_lines = [] + + start_time = time.time() + while True: + # Check for timeout + if time.time() - start_time > timeout_seconds_fixed: + process.kill() + status_text_fixed.error(f"Computation timed out after {timeout_seconds_fixed} seconds") + success = False + break + + # Try to read a line (non-blocking) + line = process.stdout.readline() + if not line and process.poll() is not None: + break + + if line: + stdout_lines.append(line) + if line.startswith("PROGRESS:"): + try: + # Update progress bar + progress_value = float(line.split(":")[1].strip()) + progress_bar_fixed.progress(progress_value) + status_text_fixed.text(f"Calculating... {int(progress_value * 100)}% complete") + except: + pass + elif line: + status_text_fixed.text(line.strip()) + + # Get the return code and stderr + returncode = process.poll() + stderr = process.stderr.read() + + if returncode != 0: + success = False + st.error(f"Error executing the analysis: {stderr}") + with st.expander("Error Details"): + st.code(stderr) - # Display statistics in a cleaner way - st.markdown('
', unsafe_allow_html=True) - col1, col2, col3, col4 = st.columns(4) - with col1: - st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}") - with col2: - st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}") - with col3: - st.metric("Max Theoretical", f"{theoretical_max.max():.4f}") - with col4: - st.metric("Min Theoretical", f"{theoretical_min.min():.4f}") - st.markdown('
', unsafe_allow_html=True) + if success: + progress_bar_fixed.progress(1.0) + status_text_fixed.text("Analysis complete! Generating visualization...") - except json.JSONDecodeError as e: - st.error(f"Error parsing JSON results: {str(e)}") - if os.path.exists(data_file): - with open(data_file, 'r') as f: - content = f.read() - st.code(content[:1000] + "..." if len(content) > 1000 else content) - - except Exception as e: - st.error(f"An error occurred: {str(e)}") - if debug_mode: - st.exception(e) - - else: - # Try to load existing data if available - data_file = os.path.join(output_dir, "eigenvalue_data.json") - if os.path.exists(data_file): - try: - with open(data_file, 'r') as f: - data = json.load(f) - - # Process data - convert string values to numeric - beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']]) - max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) - min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) - theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) - theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) - - # Create an interactive plot using Plotly - fig = go.Figure() - - # Add traces for each line - fig.add_trace(go.Scatter( - x=beta_values, - y=max_eigenvalues, - mode='lines+markers', - name='Empirical Max Eigenvalue', - line=dict(color=color_max, width=3), - marker=dict( - symbol='circle', - size=8, - color=color_max, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Max' - )) - - fig.add_trace(go.Scatter( - x=beta_values, - y=min_eigenvalues, - mode='lines+markers', - name='Empirical Min Eigenvalue', - line=dict(color=color_min, width=3), - marker=dict( - symbol='circle', - size=8, - color=color_min, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Empirical Min' - )) - - fig.add_trace(go.Scatter( - x=beta_values, - y=theoretical_max, - mode='lines+markers', - name='Theoretical Max', - line=dict(color=color_theory_max, width=3), - marker=dict( - symbol='diamond', - size=8, - color=color_theory_max, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Max' - )) - - fig.add_trace(go.Scatter( - x=beta_values, - y=theoretical_min, - mode='lines+markers', - name='Theoretical Min', - line=dict(color=color_theory_min, width=3), - marker=dict( - symbol='diamond', - size=8, - color=color_theory_min, - line=dict(color='white', width=1) - ), - hovertemplate='Ξ²: %{x:.3f}
Value: %{y:.6f}Theoretical Min' - )) - - # Configure layout for better appearance - fig.update_layout( - title={ - 'text': f'Eigenvalue Analysis (Previous Result)', - 'font': {'size': 24, 'color': '#0e1117'}, - 'y': 0.95, - 'x': 0.5, - 'xanchor': 'center', - 'yanchor': 'top' - }, - xaxis={ - 'title': {'text': 'Ξ² Parameter', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True - }, - yaxis={ - 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True - }, - plot_bgcolor='rgba(250, 250, 250, 0.8)', - paper_bgcolor='rgba(255, 255, 255, 0.8)', - hovermode='closest', - legend={ - 'font': {'size': 14}, - 'bgcolor': 'rgba(255, 255, 255, 0.9)', - 'bordercolor': 'rgba(200, 200, 200, 0.5)', - 'borderwidth': 1 - }, - margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, - height=600 - ) - - # Display the interactive plot in Streamlit - st.plotly_chart(fig, use_container_width=True) - st.info("This is the previous analysis result. Adjust parameters and click 'Generate Analysis' to create a new visualization.") - - except Exception as e: - st.info("Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.") + # Check if the output file was created + if not os.path.exists(data_file_fixed): + st.error(f"Output file not created: {data_file_fixed}") + st.stop() + + try: + # Load the results from the JSON file + with open(data_file_fixed, 'r') as f: + data = json.load(f) + + # Process data - convert string values to numeric + a_values = np.array([safe_convert_to_numeric(x) for x in data['a_values']]) + max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) + min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) + theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) + theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) + + # Create an interactive plot using Plotly + fig = go.Figure() + + # Add traces for each line + fig.add_trace(go.Scatter( + x=a_values, + y=max_eigenvalues, + mode='lines+markers', + name='Empirical Max Eigenvalue', + line=dict(color=color_max, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_max, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Empirical Max' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=min_eigenvalues, + mode='lines+markers', + name='Empirical Min Eigenvalue', + line=dict(color=color_min, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_min, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Empirical Min' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=theoretical_max, + mode='lines+markers', + name='Theoretical Max', + line=dict(color=color_theory_max, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_max, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Theoretical Max' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=theoretical_min, + mode='lines+markers', + name='Theoretical Min', + line=dict(color=color_theory_min, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_min, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Theoretical Min' + )) + + # Configure layout for better appearance + fig.update_layout( + title={ + 'text': f'Fixed Ξ² Analysis: n={n_fixed}, p={p_fixed}, Ξ²={beta_fixed:.3f}, y={y_fixed:.4f}', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'a Parameter', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + yaxis={ + 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + plot_bgcolor='rgba(250, 250, 250, 0.8)', + paper_bgcolor='rgba(255, 255, 255, 0.8)', + hovermode='closest', + legend={ + 'font': {'size': 14}, + 'bgcolor': 'rgba(255, 255, 255, 0.9)', + 'bordercolor': 'rgba(200, 200, 200, 0.5)', + 'borderwidth': 1 + }, + margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, + height=600, + ) + + # Add custom modebar buttons + fig.update_layout( + modebar_add=[ + 'drawline', 'drawopenpath', 'drawclosedpath', + 'drawcircle', 'drawrect', 'eraseshape' + ], + modebar_remove=['lasso2d', 'select2d'], + dragmode='zoom' + ) + + # Clear progress container + progress_container_fixed.empty() + + # Display the interactive plot in Streamlit + st.plotly_chart(fig, use_container_width=True) + + # Display statistics in a cleaner way + st.markdown('
', unsafe_allow_html=True) + col1, col2, col3, col4 = st.columns(4) + with col1: + st.metric("Max Empirical", f"{max_eigenvalues.max():.4f}") + with col2: + st.metric("Min Empirical", f"{min_eigenvalues.min():.4f}") + with col3: + st.metric("Max Theoretical", f"{theoretical_max.max():.4f}") + with col4: + st.metric("Min Theoretical", f"{theoretical_min.min():.4f}") + st.markdown('
', unsafe_allow_html=True) + + # Add interpretation section + with st.expander("Fixed Ξ² Analysis Explanation", expanded=False): + st.markdown(f""" + ### Understanding Fixed Ξ² Analysis + + In this analysis, Ξ² is fixed at **{beta_fixed:.3f}** while parameter **a** varies from **{a_min:.2f}** to **{a_max:.2f}**. + + **What this shows:** + - How eigenvalue bounds change with parameter **a** when the mixture proportion Ξ² is constant + - The relationship between the spike eigenvalue parameter **a** and the empirical eigenvalue distribution + - Validation of theoretical predictions for varying **a** at fixed Ξ² + + **Key insights:** + - As **a** increases, the maximum eigenvalue generally increases + - The minimum eigenvalue behavior depends on the specific value of Ξ² + - The gap between theoretical and empirical bounds shows finite-sample effects + """) + + except json.JSONDecodeError as e: + st.error(f"Error parsing JSON results: {str(e)}") + if os.path.exists(data_file_fixed): + with open(data_file_fixed, 'r') as f: + content = f.read() + st.code(content[:1000] + "..." if len(content) > 1000 else content) + + except Exception as e: + st.error(f"An error occurred: {str(e)}") + if debug_mode_fixed: + st.exception(e) + else: - # Show placeholder - st.info("Set parameters and click 'Generate Eigenvalue Analysis' to create a visualization.") - - st.markdown('
', unsafe_allow_html=True) + # Try to load existing data if available + data_file_fixed = os.path.join(output_dir, "eigenvalue_fixed_beta_data.json") + if os.path.exists(data_file_fixed): + try: + with open(data_file_fixed, 'r') as f: + data = json.load(f) + + # Process data - convert string values to numeric + a_values = np.array([safe_convert_to_numeric(x) for x in data['a_values']]) + max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']]) + min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']]) + theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']]) + theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']]) + + # Create an interactive plot using Plotly + fig = go.Figure() + + # Add traces for each line + fig.add_trace(go.Scatter( + x=a_values, + y=max_eigenvalues, + mode='lines+markers', + name='Empirical Max Eigenvalue', + line=dict(color=color_max, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_max, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Empirical Max' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=min_eigenvalues, + mode='lines+markers', + name='Empirical Min Eigenvalue', + line=dict(color=color_min, width=3), + marker=dict( + symbol='circle', + size=8, + color=color_min, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Empirical Min' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=theoretical_max, + mode='lines+markers', + name='Theoretical Max', + line=dict(color=color_theory_max, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_max, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Theoretical Max' + )) + + fig.add_trace(go.Scatter( + x=a_values, + y=theoretical_min, + mode='lines+markers', + name='Theoretical Min', + line=dict(color=color_theory_min, width=3), + marker=dict( + symbol='diamond', + size=8, + color=color_theory_min, + line=dict(color='white', width=1) + ), + hovertemplate='a: %{x:.3f}
Value: %{y:.6f}Theoretical Min' + )) + + # Configure layout for better appearance + fig.update_layout( + title={ + 'text': f'Fixed Ξ² Analysis (Previous Result)', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'a Parameter', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + yaxis={ + 'title': {'text': 'Eigenvalues', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True + }, + plot_bgcolor='rgba(250, 250, 250, 0.8)', + paper_bgcolor='rgba(255, 255, 255, 0.8)', + hovermode='closest', + legend={ + 'font': {'size': 14}, + 'bgcolor': 'rgba(255, 255, 255, 0.9)', + 'bordercolor': 'rgba(200, 200, 200, 0.5)', + 'borderwidth': 1 + }, + margin={'l': 60, 'r': 30, 't': 100, 'b': 60}, + height=600 + ) + + # Display the interactive plot in Streamlit + st.plotly_chart(fig, use_container_width=True) + st.info("This is the previous analysis result. Adjust parameters and click 'Generate Fixed Ξ² Analysis' to create a new visualization.") + + except Exception as e: + st.info("Set parameters and click 'Generate Fixed Ξ² Analysis' to create a visualization.") + else: + # Show placeholder + st.info("Set parameters and click 'Generate Fixed Ξ² Analysis' to create a visualization.") + + st.markdown('
', unsafe_allow_html=True) # ----- Tab 2: Complex Root Analysis ----- @@ -2479,6 +2367,7 @@ with tab2: with col2: st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}") st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}") + # Add footer with instructions st.markdown(""" """, unsafe_allow_html=True) \ No newline at end of file