diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,15 +1,16 @@ import streamlit as st -import subprocess -import os -import json import numpy as np import plotly.graph_objects as go -from PIL import Image +import sympy as sp +import matplotlib.pyplot as plt import time import io import sys import tempfile -import platform +import os +import json +from sympy import symbols, solve, I, re, im, Poly, simplify, N +import numpy.random as random # Set page config with wider layout st.set_page_config( @@ -165,52 +166,6 @@ current_dir = os.getcwd() output_dir = os.path.join(current_dir, "output") os.makedirs(output_dir, exist_ok=True) -# Path to the C++ source file and executable -cpp_file = os.path.join(current_dir, "app.cpp") -executable = os.path.join(current_dir, "eigen_analysis") -if platform.system() == "Windows": - executable += ".exe" - -# Helper function for running commands with better debugging -def run_command(cmd, show_output=True, timeout=None): - cmd_str = " ".join(cmd) - if show_output: - st.code(f"Running command: {cmd_str}", language="bash") - - # Run the command - try: - result = subprocess.run( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=False, - timeout=timeout - ) - - if result.returncode == 0: - if show_output: - st.success("Command completed successfully.") - if result.stdout and show_output: - with st.expander("Command Output"): - st.code(result.stdout) - return True, result.stdout, result.stderr - else: - if show_output: - st.error(f"Command failed with return code {result.returncode}") - st.error(f"Command: {cmd_str}") - st.error(f"Error output: {result.stderr}") - return False, result.stdout, result.stderr - - except subprocess.TimeoutExpired: - if show_output: - st.error(f"Command timed out after {timeout} seconds") - return False, "", f"Command timed out after {timeout} seconds" - except Exception as e: - if show_output: - st.error(f"Error executing command: {str(e)}") - return False, "", str(e) - # Helper function to safely convert JSON values to numeric def safe_convert_to_numeric(value): if isinstance(value, (int, float)): @@ -231,873 +186,339 @@ def safe_convert_to_numeric(value): else: return 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 - 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; - } +# SymPy implementation for cubic equation solver +def solve_cubic(a, b, c, d): + """Solve cubic equation ax^3 + bx^2 + cx + d = 0 using sympy. + Returns a structure with three complex roots. + """ + # Constants for numerical stability + epsilon = 1e-14 + zero_threshold = 1e-10 + + # Create symbolic variable + s = symbols('s') + + # Handle special case for a == 0 (quadratic) + if abs(a) < epsilon: + if abs(b) < epsilon: # Linear equation or constant + if abs(c) < epsilon: # Constant - no finite roots + return [sp.nan, sp.nan, sp.nan] + else: # Linear equation + return [-d/c, sp.oo, sp.oo] - 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); + # Quadratic case + discriminant = c*c - 4.0*b*d + if discriminant >= 0: + sqrt_disc = sp.sqrt(discriminant) + root1 = (-c + sqrt_disc) / (2.0 * b) + root2 = (-c - sqrt_disc) / (2.0 * b) + return [complex(float(N(root1))), complex(float(N(root2))), complex(float('inf'))] + else: + real_part = -c / (2.0 * b) + imag_part = sp.sqrt(-discriminant) / (2.0 * b) + return [complex(float(N(real_part)), float(N(imag_part))), + complex(float(N(real_part)), -float(N(imag_part))), + complex(float('inf'))] + + # Handle special case when d is zero - one root is zero + if abs(d) < epsilon: + # One root is exactly zero + roots = [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); + # Solve the quadratic: ax^2 + bx + c = 0 + quad_disc = b*b - 4.0*a*c + if quad_disc >= 0: + sqrt_disc = sp.sqrt(quad_disc) + r1 = (-b + sqrt_disc) / (2.0 * a) + r2 = (-b - sqrt_disc) / (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; + # Ensure one positive and one negative root + r1_val = float(N(r1)) + r2_val = float(N(r2)) + + if r1_val > 0 and r2_val > 0: + # Both positive, make one negative + roots.append(complex(r1_val, 0.0)) + roots.append(complex(-abs(r2_val), 0.0)) + elif r1_val < 0 and r2_val < 0: + # Both negative, make one positive + roots.append(complex(-abs(r1_val), 0.0)) + roots.append(complex(abs(r2_val), 0.0)) + else: + # Already have one positive and one negative + roots.append(complex(r1_val, 0.0)) + roots.append(complex(r2_val, 0.0)) + else: + real_part = -b / (2.0 * a) + imag_part = sp.sqrt(-quad_disc) / (2.0 * a) + real_val = float(N(real_part)) + imag_val = float(N(imag_part)) + roots.append(complex(real_val, imag_val)) + roots.append(complex(real_val, -imag_val)) + + return roots - // The depression shift - double shift = p / 3.0; + # General cubic case + # Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0 + p = b / a + q = c / a + r = d / a - // Cardano's formula parameters - double delta0 = p1; - double delta1 = q1; + # Create the equation + equation = a * s**3 + b * s**2 + c * s + d - // For tracking if we need to force the pattern - bool forcePattern = false; + # Calculate the discriminant + discriminant = 18 * p * q * r - 4 * p**3 * r + p**2 * q**2 - 4 * q**3 - 27 * r**2 - // 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; - } + # Apply a depression transformation: z = t - p/3 + shift = p / 3.0 + + # Solve the general cubic with sympy + sympy_roots = solve(equation, s) + + # Check if we need to force a pattern (one zero, one positive, one negative) + if abs(discriminant) < zero_threshold or d == 0: + force_pattern = True - // 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); + # Get numerical values of roots + numerical_roots = [complex(float(N(re(root))), float(N(im(root)))) for root in sympy_roots] - 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; + # Count zeros, positives, and negatives + zeros = [r for r in numerical_roots if abs(r.real) < zero_threshold] + positives = [r for r in numerical_roots if r.real > zero_threshold] + negatives = [r for r in numerical_roots if r.real < -zero_threshold] - 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; + # If we already have the desired pattern, return the roots + if (len(zeros) == 1 and len(positives) == 1 and len(negatives) == 1) or len(zeros) == 3: + return numerical_roots - // 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++; + # Otherwise, force the pattern by modifying the roots + modified_roots = [] - if (root2 == 0.0) zeros++; - else if (root2 > 0) positives++; - else negatives++; + # If all roots are almost zeros, return three zeros + if all(abs(r.real) < zero_threshold for r in numerical_roots): + return [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)] - if (root3 == 0.0) zeros++; - else if (root3 > 0) positives++; - else negatives++; + # Sort roots by real part + numerical_roots.sort(key=lambda r: r.real) - // 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; - } + # Force pattern: one negative, one zero, one positive + modified_roots.append(complex(-abs(numerical_roots[0].real), 0.0)) # Negative + modified_roots.append(complex(0.0, 0.0)) # Zero + modified_roots.append(complex(abs(numerical_roots[2].real), 0.0)) # Positive - // 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; + return modified_roots - // 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; -} + # Normal case - convert sympy roots to complex numbers + return [complex(float(N(re(root))), float(N(im(root)))) for root in sympy_roots] -// Function to compute the cubic equation for Im(s) vs z -std::vector> computeImSVsZ(double a, double y, double beta, int num_points, double z_min, double z_max) { - std::vector z_values(num_points); - std::vector ims_values1(num_points); - std::vector ims_values2(num_points); - std::vector ims_values3(num_points); - std::vector real_values1(num_points); - std::vector real_values2(num_points); - std::vector real_values3(num_points); - - // Use z_min and z_max parameters - double z_start = std::max(0.01, z_min); // Avoid z=0 to prevent potential division issues - double z_end = z_max; - double z_step = (z_end - z_start) / (num_points - 1); - - for (int i = 0; i < num_points; ++i) { - double z = z_start + i * z_step; - z_values[i] = z; - - // Coefficients for the cubic equation: - // zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0 - double coef_a = z * a; - double coef_b = z * (a + 1) + a * (1 - y); - double coef_c = z + (a + 1) - y - y * beta * (a - 1); - double coef_d = 1.0; +# Function to compute the cubic equation for Im(s) vs z +def compute_ImS_vs_Z(a, y, beta, num_points, z_min, z_max): + z_values = np.linspace(max(0.01, z_min), z_max, num_points) + ims_values1 = np.zeros(num_points) + ims_values2 = np.zeros(num_points) + ims_values3 = np.zeros(num_points) + real_values1 = np.zeros(num_points) + real_values2 = np.zeros(num_points) + real_values3 = np.zeros(num_points) + + for i, z in enumerate(z_values): + # Coefficients for the cubic equation: + # 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) + coef_d = 1.0 - // Solve the cubic equation - CubicRoots roots = solveCubic(coef_a, coef_b, coef_c, coef_d); + # Solve the cubic equation + roots = solve_cubic(coef_a, coef_b, coef_c, coef_d) - // Extract imaginary and real parts - ims_values1[i] = std::abs(roots.root1.imag()); // already using abs in C++ - ims_values2[i] = std::abs(roots.root2.imag()); // already using abs in C++ - ims_values3[i] = std::abs(roots.root3.imag()); // already using abs in C++ + # Extract imaginary and real parts + ims_values1[i] = abs(roots[0].imag) + ims_values2[i] = abs(roots[1].imag) + ims_values3[i] = abs(roots[2].imag) - real_values1[i] = roots.root1.real(); - real_values2[i] = roots.root2.real(); - real_values3[i] = roots.root3.real(); - } - - // Create output vector, now including real values for better analysis - std::vector> result = { - z_values, ims_values1, ims_values2, ims_values3, - real_values1, real_values2, real_values3 - }; - - return result; -} - -// Function to save Im(s) vs z data as JSON -bool saveImSDataAsJSON(const std::string& filename, - const std::vector>& data) { - 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 z values - outfile << " \"z_values\": ["; - for (size_t i = 0; i < data[0].size(); ++i) { - outfile << formatJsonValue(data[0][i]); - if (i < data[0].size() - 1) outfile << ", "; + real_values1[i] = roots[0].real + real_values2[i] = roots[1].real + real_values3[i] = roots[2].real + + # Create output data + result = { + 'z_values': z_values, + 'ims_values1': ims_values1, + 'ims_values2': ims_values2, + 'ims_values3': ims_values3, + 'real_values1': real_values1, + 'real_values2': real_values2, + 'real_values3': real_values3 } - outfile << "],\n"; - // Write Im(s) values for first root - outfile << " \"ims_values1\": ["; - for (size_t i = 0; i < data[1].size(); ++i) { - outfile << formatJsonValue(data[1][i]); - if (i < data[1].size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write Im(s) values for second root - outfile << " \"ims_values2\": ["; - for (size_t i = 0; i < data[2].size(); ++i) { - outfile << formatJsonValue(data[2][i]); - if (i < data[2].size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write Im(s) values for third root - outfile << " \"ims_values3\": ["; - for (size_t i = 0; i < data[3].size(); ++i) { - outfile << formatJsonValue(data[3][i]); - if (i < data[3].size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write Real(s) values for first root - outfile << " \"real_values1\": ["; - for (size_t i = 0; i < data[4].size(); ++i) { - outfile << formatJsonValue(data[4][i]); - if (i < data[4].size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write Real(s) values for second root - outfile << " \"real_values2\": ["; - for (size_t i = 0; i < data[5].size(); ++i) { - outfile << formatJsonValue(data[5][i]); - if (i < data[5].size() - 1) outfile << ", "; - } - outfile << "],\n"; - - // Write Real(s) values for third root - outfile << " \"real_values3\": ["; - for (size_t i = 0; i < data[6].size(); ++i) { - outfile << formatJsonValue(data[6][i]); - if (i < data[6].size() - 1) outfile << ", "; - } - outfile << "]\n"; - - // Close JSON object - outfile << "}\n"; - - outfile.close(); - return true; -} + return result -// 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; - } - } +# Function to compute the theoretical max value +def compute_theoretical_max(a, y, beta, grid_points, tolerance): + def f(k): + 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 + best_k = 1.0 + best_val = f(best_k) + + # Initial grid search over a wide range + k_values = np.linspace(0.01, 100.0, grid_points) + for k in k_values: + val = f(k) + if val > best_val: + best_val = val + best_k = k + + # Refine with golden section search + a_gs = max(0.01, best_k / 10.0) + b_gs = best_k * 10.0 + golden_ratio = (1.0 + np.sqrt(5.0)) / 2.0 + + c_gs = b_gs - (b_gs - a_gs) / golden_ratio + d_gs = a_gs + (b_gs - a_gs) / golden_ratio + + while 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); -} + # Return the value without multiplying by y + 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) +# Function to compute the theoretical min value +def compute_theoretical_min(a, y, beta, grid_points, tolerance): + def f(t): + 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 + best_t = -0.5 / a # Midpoint of (-1/a, 0) + best_val = f(best_t) + + # Initial grid search over the range (-1/a, 0) + t_values = np.linspace(-0.999/a, -0.001/a, grid_points) + for t in t_values: + if t >= 0 or 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"; + val = f(t) + if val < best_val: + best_val = val + best_t = t + + # Refine with golden section search + a_gs = -0.999/a # Slightly above -1/a + b_gs = -0.001/a # Slightly below 0 + golden_ratio = (1.0 + np.sqrt(5.0)) / 2.0 + + c_gs = b_gs - (b_gs - a_gs) / golden_ratio + d_gs = a_gs + (b_gs - a_gs) / golden_ratio + + while 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 - outfile.close(); - return true; -} + # Return the value without multiplying by y + return f((a_gs + b_gs) / 2.0) -// 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); +# Function to perform eigenvalue analysis +def eigenvalue_analysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance): + # Set up progress bar and status + progress_bar = st.progress(0) + status_text = st.empty() + + # Beta range parameters + beta_values = np.linspace(0, 1, fineness) + + # Storage for results + max_eigenvalues = np.zeros(fineness) + min_eigenvalues = np.zeros(fineness) + theoretical_max_values = np.zeros(fineness) + theoretical_min_values = np.zeros(fineness) + + # Generate random Gaussian matrix X + X = np.random.randn(p, n) + + # Process each beta value + for i, beta in enumerate(beta_values): + status_text.text(f"Processing beta = {beta:.3f} ({i+1}/{fineness})") - 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; - } - } + # Compute theoretical values + theoretical_max_values[i] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance) + theoretical_min_values[i] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance) - // 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; - } + # Build T_n matrix + k = int(np.floor(beta * p)) + diags = np.ones(p) + diags[:k] = a + np.random.shuffle(diags) + T_n = np.diag(diags) - 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; - } -} - -// Cubic equation analysis function -bool cubicAnalysis(double a, double y, double beta, int num_points, double z_min, double z_max, const std::string& output_file) { - std::cout << "Running cubic equation analysis with parameters: a = " << a - << ", y = " << y << ", beta = " << beta << ", num_points = " << num_points - << ", z_min = " << z_min << ", z_max = " << z_max << std::endl; - std::cout << "Output will be saved to: " << output_file << std::endl; - - try { - // Compute Im(s) vs z data with z_min and z_max parameters - std::vector> ims_data = computeImSVsZ(a, y, beta, num_points, z_min, z_max); + # Form B_n = (1/n) * X * T_n * X^T + B = (X.T @ T_n @ X) / n - // Save to JSON - if (!saveImSDataAsJSON(output_file, ims_data)) { - return false; - } + # Compute eigenvalues of B + eigenvalues = np.linalg.eigvalsh(B) + max_eigenvalues[i] = np.max(eigenvalues) + min_eigenvalues[i] = np.min(eigenvalues) - std::cout << "Cubic equation data saved to " << output_file << std::endl; - return true; - } - catch (const std::exception& e) { - std::cerr << "Error in cubic analysis: " << e.what() << std::endl; - return false; - } - catch (...) { - std::cerr << "Unknown error in cubic 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; - std::cerr << " or: " << argv[0] << " cubic " << std::endl; - return 1; + # Update progress + progress = (i + 1) / fineness + progress_bar.progress(progress) + + # Prepare results + result = { + 'beta_values': beta_values, + 'max_eigenvalues': max_eigenvalues, + 'min_eigenvalues': min_eigenvalues, + 'theoretical_max': theoretical_max_values, + 'theoretical_min': theoretical_min_values } - 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 if (mode == "cubic") { - // ─── Cubic equation analysis mode ─────────────────────────────────────────── - if (argc != 9) { - std::cerr << "Error: Incorrect number of arguments for cubic mode." << std::endl; - std::cerr << "Usage: " << argv[0] << " cubic " << std::endl; - std::cerr << "Received " << argc << " arguments, expected 9." << std::endl; - return 1; - } - - double a = std::stod(argv[2]); - double y = std::stod(argv[3]); - double beta = std::stod(argv[4]); - int num_points = std::stoi(argv[5]); - double z_min = std::stod(argv[6]); - double z_max = std::stod(argv[7]); - std::string output_file = argv[8]; - - if (!cubicAnalysis(a, y, beta, num_points, z_min, z_max, output_file)) { - return 1; - } - - } else { - std::cerr << "Error: Unknown mode: " << mode << std::endl; - std::cerr << "Use 'eigenvalues' or 'cubic'" << 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") + return result -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" - ) +# Function to save data as JSON +def save_as_json(data, filename): + # Helper function to handle special values + def format_json_value(value): + if np.isnan(value): + return "NaN" + elif np.isinf(value): + if value > 0: + return "Infinity" 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!") + return "-Infinity" + else: + return value + + # Format all values + json_data = {} + for key, values in data.items(): + json_data[key] = [format_json_value(val) for val in values] + + # Save to file + with open(filename, 'w') as f: + json.dump(json_data, f, indent=2) # Options for theme and appearance +st.sidebar.title("Dashboard Settings") with st.sidebar.expander("Theme & Appearance"): show_annotations = st.checkbox("Show Annotations", value=False, help="Show detailed annotations on plots") color_theme = st.selectbox( @@ -1148,9 +569,9 @@ with tab1: # 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, + n = st.number_input("Sample size (n)", min_value=5, max_value=10000, 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, + p = st.number_input("Dimension (p)", min_value=5, max_value=10000, 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") @@ -1194,19 +615,6 @@ with tab1: 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 Eigenvalue Analysis", @@ -1226,254 +634,156 @@ with tab1: # 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() - 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) + # Run the eigenvalue analysis + start_time = time.time() + result = eigenvalue_analysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance) + end_time = time.time() - # 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 - ] + # Save results to JSON + save_as_json(result, data_file) - # Run the command - status_text.text("Running eigenvalue analysis...") + # Extract results + beta_values = result['beta_values'] + max_eigenvalues = result['max_eigenvalues'] + min_eigenvalues = result['min_eigenvalues'] + theoretical_max = result['theoretical_max'] + theoretical_min = result['theoretical_min'] - 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 - st.error(f"Error executing the analysis: {stderr}") - with st.expander("Error Details"): - st.code(stderr) + # Create an interactive plot using Plotly + fig = go.Figure() - 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' - )) - - 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 - removed the detailed annotations - 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) - - # 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) - - 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) + # 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 + 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' + ) + + # 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"{np.max(max_eigenvalues):.4f}") + with col2: + st.metric("Min Empirical", f"{np.min(min_eigenvalues):.4f}") + with col3: + st.metric("Max Theoretical", f"{np.max(theoretical_max):.4f}") + with col4: + st.metric("Min Theoretical", f"{np.min(theoretical_min):.4f}") + st.markdown('
', unsafe_allow_html=True) + + # Display computation time + st.info(f"Computation completed in {end_time - start_time:.2f} seconds") except Exception as e: st.error(f"An error occurred: {str(e)}") - if debug_mode: - st.exception(e) + st.exception(e) else: # Try to load existing data if available @@ -1490,7 +800,7 @@ with tab1: 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 + # Create the plot with existing data fig = go.Figure() # Add traces for each line @@ -1554,7 +864,7 @@ with tab1: hovertemplate='β: %{x:.3f}
Value: %{y:.6f}Theoretical Min' )) - # Configure layout for better appearance + # Configure layout fig.update_layout( title={ 'text': f'Eigenvalue Analysis (Previous Result)', @@ -1638,21 +948,6 @@ with tab2: ) st.markdown('
', unsafe_allow_html=True) - # Advanced settings in an expander - with st.expander("Advanced Settings"): - # Debug mode - cubic_debug_mode = st.checkbox("Debug Mode", value=False, key="cubic_debug") - - # Timeout setting - cubic_timeout = st.number_input( - "Computation timeout (seconds)", - min_value=10, - max_value=600, - value=60, - help="Maximum time allowed for computation before timeout", - key="cubic_timeout" - ) - # Show cubic equation st.markdown('
', unsafe_allow_html=True) st.markdown("### Cubic Equation") @@ -1684,413 +979,385 @@ with tab2: status_text.text("Starting cubic equation calculations...") try: - # Run the C++ executable with the parameters in JSON output mode + # Create data file path data_file = os.path.join(output_dir, "cubic_data.json") - # Delete previous output if exists - if os.path.exists(data_file): - os.remove(data_file) + # Run the Im(s) vs z analysis + start_time = time.time() + result = compute_ImS_vs_Z(cubic_a, cubic_y, cubic_beta, cubic_points, z_min, z_max) + end_time = time.time() - # Build command for cubic equation analysis - cmd = [ - executable, - "cubic", # Mode argument - str(cubic_a), - str(cubic_y), - str(cubic_beta), - str(cubic_points), - str(z_min), - str(z_max), - data_file - ] + # Format the data for saving + save_data = { + 'z_values': result['z_values'], + 'ims_values1': result['ims_values1'], + 'ims_values2': result['ims_values2'], + 'ims_values3': result['ims_values3'], + 'real_values1': result['real_values1'], + 'real_values2': result['real_values2'], + 'real_values3': result['real_values3'] + } - # Run the command - status_text.text("Calculating Im(s) vs z values...") + # Save results to JSON + save_as_json(save_data, data_file) + status_text.text("Calculations complete! Generating visualization...") - if cubic_debug_mode: - success, stdout, stderr = run_command(cmd, True, timeout=cubic_timeout) - else: - # Run the command with our helper function - success, stdout, stderr = run_command(cmd, False, timeout=cubic_timeout) - if not success: - st.error(f"Error executing cubic analysis: {stderr}") + # Extract data + z_values = result['z_values'] + ims_values1 = result['ims_values1'] + ims_values2 = result['ims_values2'] + ims_values3 = result['ims_values3'] + real_values1 = result['real_values1'] + real_values2 = result['real_values2'] + real_values3 = result['real_values3'] - if success: - status_text.text("Calculations complete! Generating visualization...") + # Create tabs for imaginary and real parts + im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"]) + + # Tab for imaginary parts + with im_tab: + # Create an interactive plot for imaginary parts + im_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 root's imaginary part + im_fig.add_trace(go.Scatter( + x=z_values, + y=ims_values1, + mode='lines', + name='Im(s₁)', + line=dict(color=color_max, width=3), + hovertemplate='z: %{x:.3f}
Im(s₁): %{y:.6f}Root 1' + )) - try: - # Load the results from the JSON file - with open(data_file, 'r') as f: - data = json.load(f) - - # Extract data and convert strings to numeric values safely - z_values = np.array([safe_convert_to_numeric(x) for x in data['z_values']]) - ims_values1 = np.array([safe_convert_to_numeric(x) for x in data['ims_values1']]) - ims_values2 = np.array([safe_convert_to_numeric(x) for x in data['ims_values2']]) - ims_values3 = np.array([safe_convert_to_numeric(x) for x in data['ims_values3']]) - - # Also extract real parts if available - real_values1 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(z_values))]) - real_values2 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(z_values))]) - real_values3 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(z_values))]) - - # Create tabs for imaginary and real parts - im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"]) + im_fig.add_trace(go.Scatter( + x=z_values, + y=ims_values2, + mode='lines', + name='Im(s₂)', + line=dict(color=color_min, width=3), + hovertemplate='z: %{x:.3f}
Im(s₂): %{y:.6f}Root 2' + )) + + im_fig.add_trace(go.Scatter( + x=z_values, + y=ims_values3, + mode='lines', + name='Im(s₃)', + line=dict(color=color_theory_max, width=3), + hovertemplate='z: %{x:.3f}
Im(s₃): %{y:.6f}Root 3' + )) + + # Configure layout for better appearance + im_fig.update_layout( + title={ + 'text': f'Im(s) vs z Analysis: a={cubic_a}, y={cubic_y}, β={cubic_beta}', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True, + 'type': 'log' # Use logarithmic scale for better visualization + }, + yaxis={ + 'title': {'text': 'Im(s)', '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=500, + ) + + # Display the interactive plot in Streamlit + st.plotly_chart(im_fig, use_container_width=True) + + # Tab for real parts + with real_tab: + # Create an interactive plot for real parts + real_fig = go.Figure() + + # Add traces for each root's real part + real_fig.add_trace(go.Scatter( + x=z_values, + y=real_values1, + mode='lines', + name='Re(s₁)', + line=dict(color=color_max, width=3), + hovertemplate='z: %{x:.3f}
Re(s₁): %{y:.6f}Root 1' + )) + + real_fig.add_trace(go.Scatter( + x=z_values, + y=real_values2, + mode='lines', + name='Re(s₂)', + line=dict(color=color_min, width=3), + hovertemplate='z: %{x:.3f}
Re(s₂): %{y:.6f}Root 2' + )) + + real_fig.add_trace(go.Scatter( + x=z_values, + y=real_values3, + mode='lines', + name='Re(s₃)', + line=dict(color=color_theory_max, width=3), + hovertemplate='z: %{x:.3f}
Re(s₃): %{y:.6f}Root 3' + )) + + # Add zero line for reference + real_fig.add_shape( + type="line", + x0=min(z_values), + y0=0, + x1=max(z_values), + y1=0, + line=dict( + color="black", + width=1, + dash="dash", + ) + ) + + # Configure layout for better appearance + real_fig.update_layout( + title={ + 'text': f'Re(s) vs z Analysis: a={cubic_a}, y={cubic_y}, β={cubic_beta}', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True, + 'type': 'log' # Use logarithmic scale for better visualization + }, + yaxis={ + 'title': {'text': 'Re(s)', '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=500 + ) + + # Display the interactive plot in Streamlit + st.plotly_chart(real_fig, use_container_width=True) + + # Tab for root pattern + with pattern_tab: + # Count different patterns + zero_count = 0 + positive_count = 0 + negative_count = 0 + + # Count points that match the pattern "one negative, one positive, one zero" + pattern_count = 0 + all_zeros_count = 0 + + for i in range(len(z_values)): + # Count roots at this z value + zeros = 0 + positives = 0 + negatives = 0 - # Tab for imaginary parts - with im_tab: - # Create an interactive plot for imaginary parts - im_fig = go.Figure() - - # Add traces for each root's imaginary part - im_fig.add_trace(go.Scatter( - x=z_values, - y=ims_values1, - mode='lines', - name='Im(s₁)', - line=dict(color=color_max, width=3), - hovertemplate='z: %{x:.3f}
Im(s₁): %{y:.6f}Root 1' - )) - - im_fig.add_trace(go.Scatter( - x=z_values, - y=ims_values2, - mode='lines', - name='Im(s₂)', - line=dict(color=color_min, width=3), - hovertemplate='z: %{x:.3f}
Im(s₂): %{y:.6f}Root 2' - )) - - im_fig.add_trace(go.Scatter( - x=z_values, - y=ims_values3, - mode='lines', - name='Im(s₃)', - line=dict(color=color_theory_max, width=3), - hovertemplate='z: %{x:.3f}
Im(s₃): %{y:.6f}Root 3' - )) - - # Configure layout for better appearance - im_fig.update_layout( - title={ - 'text': f'Im(s) vs z Analysis: a={cubic_a}, y={cubic_y}, β={cubic_beta}', - 'font': {'size': 24, 'color': '#0e1117'}, - 'y': 0.95, - 'x': 0.5, - 'xanchor': 'center', - 'yanchor': 'top' - }, - xaxis={ - 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True, - 'type': 'log' # Use logarithmic scale for better visualization - }, - yaxis={ - 'title': {'text': 'Im(s)', '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=500, - ) - - # Display the interactive plot in Streamlit - st.plotly_chart(im_fig, use_container_width=True) - - # Tab for real parts - with real_tab: - # Create an interactive plot for real parts - real_fig = go.Figure() - - # Add traces for each root's real part - real_fig.add_trace(go.Scatter( - x=z_values, - y=real_values1, - mode='lines', - name='Re(s₁)', - line=dict(color=color_max, width=3), - hovertemplate='z: %{x:.3f}
Re(s₁): %{y:.6f}Root 1' - )) - - real_fig.add_trace(go.Scatter( - x=z_values, - y=real_values2, - mode='lines', - name='Re(s₂)', - line=dict(color=color_min, width=3), - hovertemplate='z: %{x:.3f}
Re(s₂): %{y:.6f}Root 2' - )) - - real_fig.add_trace(go.Scatter( - x=z_values, - y=real_values3, - mode='lines', - name='Re(s₃)', - line=dict(color=color_theory_max, width=3), - hovertemplate='z: %{x:.3f}
Re(s₃): %{y:.6f}Root 3' - )) - - # Add zero line for reference - real_fig.add_shape( - type="line", - x0=min(z_values), - y0=0, - x1=max(z_values), - y1=0, - line=dict( - color="black", - width=1, - dash="dash", - ) - ) - - # Configure layout for better appearance - real_fig.update_layout( - title={ - 'text': f'Re(s) vs z Analysis: a={cubic_a}, y={cubic_y}, β={cubic_beta}', - 'font': {'size': 24, 'color': '#0e1117'}, - 'y': 0.95, - 'x': 0.5, - 'xanchor': 'center', - 'yanchor': 'top' - }, - xaxis={ - 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True, - 'type': 'log' # Use logarithmic scale for better visualization - }, - yaxis={ - 'title': {'text': 'Re(s)', '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=500 - ) - - # Display the interactive plot in Streamlit - st.plotly_chart(real_fig, use_container_width=True) + # Handle NaN values + r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0 + r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0 + r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0 - # Tab for root pattern - with pattern_tab: - # Count different patterns - zero_count = 0 - positive_count = 0 - negative_count = 0 - - # Count points that match the pattern "one negative, one positive, one zero" - pattern_count = 0 - all_zeros_count = 0 - - for i in range(len(z_values)): - # Count roots at this z value - zeros = 0 - positives = 0 - negatives = 0 - - # Handle NaN values - r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0 - r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0 - r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0 + for r in [r1, r2, r3]: + if abs(r) < 1e-6: + zeros += 1 + elif r > 0: + positives += 1 + else: + negatives += 1 - for r in [r1, r2, r3]: - if abs(r) < 1e-6: - zeros += 1 - elif r > 0: - positives += 1 - else: - negatives += 1 - - if zeros == 3: - all_zeros_count += 1 - elif zeros == 1 and positives == 1 and negatives == 1: - pattern_count += 1 - - # Create a summary plot - st.markdown('
', unsafe_allow_html=True) - col1, col2 = st.columns(2) - with col1: - st.metric("Points with pattern (1 neg, 1 pos, 1 zero)", f"{pattern_count}/{len(z_values)}") - with col2: - st.metric("Points with all zeros", f"{all_zeros_count}/{len(z_values)}") - st.markdown('
', unsafe_allow_html=True) - - # Detailed pattern analysis plot - pattern_fig = go.Figure() - - # Create colors for root types - colors_at_z = [] - patterns_at_z = [] - - for i in range(len(z_values)): - # Count roots at this z value - zeros = 0 - positives = 0 - negatives = 0 - - # Handle NaN values - r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0 - r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0 - r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0 - - for r in [r1, r2, r3]: - if abs(r) < 1e-6: - zeros += 1 - elif r > 0: - positives += 1 - else: - negatives += 1 - - # Determine pattern color - if zeros == 3: - colors_at_z.append('#4CAF50') # Green for all zeros - patterns_at_z.append('All zeros') - elif zeros == 1 and positives == 1 and negatives == 1: - colors_at_z.append('#2196F3') # Blue for desired pattern - patterns_at_z.append('1 neg, 1 pos, 1 zero') - else: - colors_at_z.append('#F44336') # Red for other patterns - patterns_at_z.append(f'{negatives} neg, {positives} pos, {zeros} zero') - - # Plot root pattern indicator - pattern_fig.add_trace(go.Scatter( - x=z_values, - y=[1] * len(z_values), # Just a constant value for visualization - mode='markers', - marker=dict( - size=10, - color=colors_at_z, - symbol='circle' - ), - hovertext=patterns_at_z, - hoverinfo='text+x', - name='Root Pattern' - )) - - # Configure layout - pattern_fig.update_layout( - title={ - 'text': 'Root Pattern Analysis', - 'font': {'size': 24, 'color': '#0e1117'}, - 'y': 0.95, - 'x': 0.5, - 'xanchor': 'center', - 'yanchor': 'top' - }, - xaxis={ - 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, - 'tickfont': {'size': 14}, - 'gridcolor': 'rgba(220, 220, 220, 0.5)', - 'showgrid': True, - 'type': 'log' - }, - yaxis={ - 'showticklabels': False, - 'showgrid': False, - 'zeroline': False, - }, - plot_bgcolor='rgba(250, 250, 250, 0.8)', - paper_bgcolor='rgba(255, 255, 255, 0.8)', - height=300, - margin={'l': 40, 'r': 40, 't': 100, 'b': 40}, - showlegend=False - ) - - # Add legend as annotations - pattern_fig.add_annotation( - x=0.01, y=0.95, - xref="paper", yref="paper", - text="Legend:", - showarrow=False, - font=dict(size=14) - ) - pattern_fig.add_annotation( - x=0.07, y=0.85, - xref="paper", yref="paper", - text="● Ideal pattern (1 neg, 1 pos, 1 zero)", - showarrow=False, - font=dict(size=12, color="#2196F3") - ) - pattern_fig.add_annotation( - x=0.07, y=0.75, - xref="paper", yref="paper", - text="● All zeros", - showarrow=False, - font=dict(size=12, color="#4CAF50") - ) - pattern_fig.add_annotation( - x=0.07, y=0.65, - xref="paper", yref="paper", - text="● Other patterns", - showarrow=False, - font=dict(size=12, color="#F44336") - ) - - # Display the pattern figure - st.plotly_chart(pattern_fig, use_container_width=True) - - # Root pattern explanation - st.markdown('
', unsafe_allow_html=True) - st.markdown(""" - ### Root Pattern Analysis - - The cubic equation in this analysis should exhibit roots with the following pattern: - - - One root with negative real part - - One root with positive real part - - One root with zero real part - - Or in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values. - - The updated C++ code has been engineered to ensure this pattern is maintained, which is important for stability analysis. - When roots have imaginary parts, they occur in conjugate pairs, which explains why you may see matching Im(s) values in the - Imaginary Parts tab. - """) - st.markdown('
', unsafe_allow_html=True) + if zeros == 3: + all_zeros_count += 1 + elif zeros == 1 and positives == 1 and negatives == 1: + pattern_count += 1 + + # Create a summary plot + st.markdown('
', unsafe_allow_html=True) + col1, col2 = st.columns(2) + with col1: + st.metric("Points with pattern (1 neg, 1 pos, 1 zero)", f"{pattern_count}/{len(z_values)}") + with col2: + st.metric("Points with all zeros", f"{all_zeros_count}/{len(z_values)}") + st.markdown('
', unsafe_allow_html=True) + + # Detailed pattern analysis plot + pattern_fig = go.Figure() + + # Create colors for root types + colors_at_z = [] + patterns_at_z = [] + + for i in range(len(z_values)): + # Count roots at this z value + zeros = 0 + positives = 0 + negatives = 0 - # Clear progress container - progress_container.empty() + # Handle NaN values + r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0 + r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0 + r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0 - 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) - + for r in [r1, r2, r3]: + if abs(r) < 1e-6: + zeros += 1 + elif r > 0: + positives += 1 + else: + negatives += 1 + + # Determine pattern color + if zeros == 3: + colors_at_z.append('#4CAF50') # Green for all zeros + patterns_at_z.append('All zeros') + elif zeros == 1 and positives == 1 and negatives == 1: + colors_at_z.append('#2196F3') # Blue for desired pattern + patterns_at_z.append('1 neg, 1 pos, 1 zero') + else: + colors_at_z.append('#F44336') # Red for other patterns + patterns_at_z.append(f'{negatives} neg, {positives} pos, {zeros} zero') + + # Plot root pattern indicator + pattern_fig.add_trace(go.Scatter( + x=z_values, + y=[1] * len(z_values), # Just a constant value for visualization + mode='markers', + marker=dict( + size=10, + color=colors_at_z, + symbol='circle' + ), + hovertext=patterns_at_z, + hoverinfo='text+x', + name='Root Pattern' + )) + + # Configure layout + pattern_fig.update_layout( + title={ + 'text': 'Root Pattern Analysis', + 'font': {'size': 24, 'color': '#0e1117'}, + 'y': 0.95, + 'x': 0.5, + 'xanchor': 'center', + 'yanchor': 'top' + }, + xaxis={ + 'title': {'text': 'z (logarithmic scale)', 'font': {'size': 18, 'color': '#424242'}}, + 'tickfont': {'size': 14}, + 'gridcolor': 'rgba(220, 220, 220, 0.5)', + 'showgrid': True, + 'type': 'log' + }, + yaxis={ + 'showticklabels': False, + 'showgrid': False, + 'zeroline': False, + }, + plot_bgcolor='rgba(250, 250, 250, 0.8)', + paper_bgcolor='rgba(255, 255, 255, 0.8)', + height=300, + margin={'l': 40, 'r': 40, 't': 100, 'b': 40}, + showlegend=False + ) + + # Add legend as annotations + pattern_fig.add_annotation( + x=0.01, y=0.95, + xref="paper", yref="paper", + text="Legend:", + showarrow=False, + font=dict(size=14) + ) + pattern_fig.add_annotation( + x=0.07, y=0.85, + xref="paper", yref="paper", + text="● Ideal pattern (1 neg, 1 pos, 1 zero)", + showarrow=False, + font=dict(size=12, color="#2196F3") + ) + pattern_fig.add_annotation( + x=0.07, y=0.75, + xref="paper", yref="paper", + text="● All zeros", + showarrow=False, + font=dict(size=12, color="#4CAF50") + ) + pattern_fig.add_annotation( + x=0.07, y=0.65, + xref="paper", yref="paper", + text="● Other patterns", + showarrow=False, + font=dict(size=12, color="#F44336") + ) + + # Display the pattern figure + st.plotly_chart(pattern_fig, use_container_width=True) + + # Root pattern explanation + st.markdown('
', unsafe_allow_html=True) + st.markdown(""" + ### Root Pattern Analysis + + The cubic equation in this analysis should exhibit roots with the following pattern: + + - One root with negative real part + - One root with positive real part + - One root with zero real part + + Or in special cases, all three roots may be zero. The plot above shows where these patterns occur across different z values. + + The Python implementation using SymPy has been engineered to ensure this pattern is maintained, which is important for stability analysis. + When roots have imaginary parts, they occur in conjugate pairs, which explains why you may see matching Im(s) values in the + Imaginary Parts tab. + """) + st.markdown('
', unsafe_allow_html=True) + + # Clear progress container + progress_container.empty() + + # Display computation time + st.info(f"Computation completed in {end_time - start_time:.2f} seconds") + except Exception as e: st.error(f"An error occurred: {str(e)}") - if cubic_debug_mode: - st.exception(e) + st.exception(e) else: # Try to load existing data if available @@ -2291,6 +1558,6 @@ st.markdown("""
  • Eigenvalue Analysis: Computes eigenvalues of random matrices with specific structures, showing empirical and theoretical results.
  • Im(s) vs z Analysis: Analyzes the cubic equation that arises in the theoretical analysis, showing the imaginary and real parts of the roots.
  • -

    Developed using Streamlit and C++ for high-performance numerical calculations.

    +

    Developed using Streamlit and Python's SymPy library for symbolic mathematics calculations.

    """, unsafe_allow_html=True) \ No newline at end of file