Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -211,6 +211,26 @@ def run_command(cmd, show_output=True, timeout=None):
|
|
211 |
st.error(f"Error executing command: {str(e)}")
|
212 |
return False, "", str(e)
|
213 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
# Check if C++ source file exists
|
215 |
if not os.path.exists(cpp_file):
|
216 |
# Create the C++ file with our improved cubic solver
|
@@ -219,7 +239,7 @@ if not os.path.exists(cpp_file):
|
|
219 |
|
220 |
# The improved C++ code with better cubic solver
|
221 |
f.write('''
|
222 |
-
// app.cpp - Modified version
|
223 |
#include <opencv2/opencv.hpp>
|
224 |
#include <algorithm>
|
225 |
#include <cmath>
|
@@ -243,16 +263,18 @@ struct CubicRoots {
|
|
243 |
};
|
244 |
|
245 |
// Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
|
246 |
-
// Improved
|
247 |
CubicRoots solveCubic(double a, double b, double c, double d) {
|
|
|
|
|
|
|
248 |
// Constants for numerical stability
|
249 |
const double epsilon = 1e-14;
|
250 |
-
const double zero_threshold = 1e-10;
|
251 |
|
252 |
// Handle special case for a == 0 (quadratic)
|
253 |
if (std::abs(a) < epsilon) {
|
254 |
-
|
255 |
-
// For a quadratic equation: bz^2 + cz + d = 0
|
256 |
if (std::abs(b) < epsilon) { // Linear equation or constant
|
257 |
if (std::abs(c) < epsilon) { // Constant - no finite roots
|
258 |
roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
|
@@ -284,163 +306,220 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
|
|
284 |
|
285 |
// Handle special case when d is zero - one root is zero
|
286 |
if (std::abs(d) < epsilon) {
|
287 |
-
//
|
288 |
-
|
289 |
-
roots.root1 = std::complex<double>(0.0, 0.0); // One root is exactly zero
|
290 |
|
291 |
// Solve the quadratic: az^2 + bz + c = 0
|
292 |
-
double
|
293 |
-
if (
|
294 |
-
double sqrtDiscriminant = std::sqrt(
|
295 |
-
|
296 |
-
|
297 |
|
298 |
-
// Ensure one positive and one negative root
|
299 |
-
if (
|
300 |
-
//
|
301 |
-
roots.
|
302 |
-
|
303 |
-
|
304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
}
|
306 |
} else {
|
307 |
double real = -b / (2.0 * a);
|
308 |
-
double imag = std::sqrt(-
|
309 |
roots.root2 = std::complex<double>(real, imag);
|
310 |
roots.root3 = std::complex<double>(real, -imag);
|
311 |
}
|
312 |
return roots;
|
313 |
}
|
314 |
|
315 |
-
// Normalize equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
|
316 |
double p = b / a;
|
317 |
double q = c / a;
|
318 |
double r = d / a;
|
319 |
|
320 |
-
//
|
321 |
-
double
|
322 |
-
double
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
const double third = 1.0 / 3.0;
|
330 |
-
const double p_over_3 = p / 3.0;
|
331 |
|
332 |
-
|
|
|
333 |
|
334 |
-
//
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
341 |
return roots;
|
342 |
}
|
343 |
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
// Check if any roots are close to zero and set them to exactly zero
|
352 |
-
if (std::abs(roots.root1.real()) < zero_threshold)
|
353 |
roots.root1 = std::complex<double>(0.0, 0.0);
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
if (roots.root1.real() > 0 && roots.root2.real() > 0) {
|
362 |
-
roots.root2 = std::complex<double>(-std::abs(roots.root2.real()), 0.0);
|
363 |
-
} else if (roots.root1.real() < 0 && roots.root2.real() < 0) {
|
364 |
-
roots.root2 = std::complex<double>(std::abs(roots.root2.real()), 0.0);
|
365 |
}
|
|
|
366 |
}
|
367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
return roots;
|
369 |
}
|
370 |
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
double
|
375 |
-
|
376 |
-
// Real root
|
377 |
-
roots.root1 = std::complex<double>(u + v - p_over_3, 0.0);
|
378 |
-
|
379 |
-
// Complex conjugate roots
|
380 |
-
double real_part = -(u + v) / 2.0 - p_over_3;
|
381 |
-
double imag_part = (u - v) * std::sqrt(3.0) / 2.0;
|
382 |
-
roots.root2 = std::complex<double>(real_part, imag_part);
|
383 |
-
roots.root3 = std::complex<double>(real_part, -imag_part);
|
384 |
-
|
385 |
-
// Check if any roots are close to zero and set them to exactly zero
|
386 |
-
if (std::abs(roots.root1.real()) < zero_threshold)
|
387 |
-
roots.root1 = std::complex<double>(0.0, 0.0);
|
388 |
-
|
389 |
-
return roots;
|
390 |
-
}
|
391 |
-
else { // Three distinct real roots
|
392 |
-
double angle = std::acos(-q1 / 2.0 * std::sqrt(-27.0 / (p1 * p1 * p1)));
|
393 |
-
double magnitude = 2.0 * std::sqrt(-p1 / 3.0);
|
394 |
|
395 |
-
|
396 |
-
double
|
397 |
-
double
|
398 |
-
double root3_val = magnitude * std::cos((angle + 2.0 * two_pi) / 3.0) - p_over_3;
|
399 |
-
|
400 |
-
// Sort roots to have one negative, one positive, one zero if possible
|
401 |
-
std::vector<double> root_vals = {root1_val, root2_val, root3_val};
|
402 |
-
std::sort(root_vals.begin(), root_vals.end());
|
403 |
|
404 |
// Check for roots close to zero
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
// Count zeros, positives, and negatives
|
412 |
int zeros = 0, positives = 0, negatives = 0;
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
else negatives++;
|
417 |
-
}
|
418 |
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
if (zeros == 0 && (positives == 0 || negatives == 0)) {
|
423 |
-
// All same sign - force the middle value to be zero
|
424 |
-
root_vals[1] = 0.0;
|
425 |
-
}
|
426 |
-
else if (zeros > 0 && positives == 0 && negatives > 0) {
|
427 |
-
// Only zeros and negatives - force one negative to be positive
|
428 |
-
if (root_vals[2] == 0.0) root_vals[1] = std::abs(root_vals[0]);
|
429 |
-
else root_vals[2] = std::abs(root_vals[0]);
|
430 |
-
}
|
431 |
-
else if (zeros > 0 && negatives == 0 && positives > 0) {
|
432 |
-
// Only zeros and positives - force one positive to be negative
|
433 |
-
if (root_vals[0] == 0.0) root_vals[1] = -std::abs(root_vals[2]);
|
434 |
-
else root_vals[0] = -std::abs(root_vals[2]);
|
435 |
-
}
|
436 |
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
roots.root3 = std::complex<double>(root_vals[2], 0.0);
|
441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
442 |
return roots;
|
443 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
444 |
}
|
445 |
|
446 |
// Function to compute the cubic equation for Im(s) vs z
|
@@ -501,13 +580,31 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
501 |
return false;
|
502 |
}
|
503 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
504 |
// Start JSON object
|
505 |
outfile << "{\n";
|
506 |
|
507 |
// Write z values
|
508 |
outfile << " \"z_values\": [";
|
509 |
for (size_t i = 0; i < data[0].size(); ++i) {
|
510 |
-
outfile << data[0][i];
|
511 |
if (i < data[0].size() - 1) outfile << ", ";
|
512 |
}
|
513 |
outfile << "],\n";
|
@@ -515,7 +612,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
515 |
// Write Im(s) values for first root
|
516 |
outfile << " \"ims_values1\": [";
|
517 |
for (size_t i = 0; i < data[1].size(); ++i) {
|
518 |
-
outfile << data[1][i];
|
519 |
if (i < data[1].size() - 1) outfile << ", ";
|
520 |
}
|
521 |
outfile << "],\n";
|
@@ -523,7 +620,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
523 |
// Write Im(s) values for second root
|
524 |
outfile << " \"ims_values2\": [";
|
525 |
for (size_t i = 0; i < data[2].size(); ++i) {
|
526 |
-
outfile << data[2][i];
|
527 |
if (i < data[2].size() - 1) outfile << ", ";
|
528 |
}
|
529 |
outfile << "],\n";
|
@@ -531,7 +628,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
531 |
// Write Im(s) values for third root
|
532 |
outfile << " \"ims_values3\": [";
|
533 |
for (size_t i = 0; i < data[3].size(); ++i) {
|
534 |
-
outfile << data[3][i];
|
535 |
if (i < data[3].size() - 1) outfile << ", ";
|
536 |
}
|
537 |
outfile << "],\n";
|
@@ -539,7 +636,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
539 |
// Write Real(s) values for first root
|
540 |
outfile << " \"real_values1\": [";
|
541 |
for (size_t i = 0; i < data[4].size(); ++i) {
|
542 |
-
outfile << data[4][i];
|
543 |
if (i < data[4].size() - 1) outfile << ", ";
|
544 |
}
|
545 |
outfile << "],\n";
|
@@ -547,7 +644,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
547 |
// Write Real(s) values for second root
|
548 |
outfile << " \"real_values2\": [";
|
549 |
for (size_t i = 0; i < data[5].size(); ++i) {
|
550 |
-
outfile << data[5][i];
|
551 |
if (i < data[5].size() - 1) outfile << ", ";
|
552 |
}
|
553 |
outfile << "],\n";
|
@@ -555,7 +652,7 @@ bool saveImSDataAsJSON(const std::string& filename,
|
|
555 |
// Write Real(s) values for third root
|
556 |
outfile << " \"real_values3\": [";
|
557 |
for (size_t i = 0; i < data[6].size(); ++i) {
|
558 |
-
outfile << data[6][i];
|
559 |
if (i < data[6].size() - 1) outfile << ", ";
|
560 |
}
|
561 |
outfile << "]\n";
|
@@ -679,13 +776,31 @@ bool save_as_json(const std::string& filename,
|
|
679 |
return false;
|
680 |
}
|
681 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
682 |
// Start JSON object
|
683 |
outfile << "{\n";
|
684 |
|
685 |
// Write beta values
|
686 |
outfile << " \"beta_values\": [";
|
687 |
for (size_t i = 0; i < beta_values.size(); ++i) {
|
688 |
-
outfile << beta_values[i];
|
689 |
if (i < beta_values.size() - 1) outfile << ", ";
|
690 |
}
|
691 |
outfile << "],\n";
|
@@ -693,7 +808,7 @@ bool save_as_json(const std::string& filename,
|
|
693 |
// Write max eigenvalues
|
694 |
outfile << " \"max_eigenvalues\": [";
|
695 |
for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
|
696 |
-
outfile << max_eigenvalues[i];
|
697 |
if (i < max_eigenvalues.size() - 1) outfile << ", ";
|
698 |
}
|
699 |
outfile << "],\n";
|
@@ -701,7 +816,7 @@ bool save_as_json(const std::string& filename,
|
|
701 |
// Write min eigenvalues
|
702 |
outfile << " \"min_eigenvalues\": [";
|
703 |
for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
|
704 |
-
outfile << min_eigenvalues[i];
|
705 |
if (i < min_eigenvalues.size() - 1) outfile << ", ";
|
706 |
}
|
707 |
outfile << "],\n";
|
@@ -709,7 +824,7 @@ bool save_as_json(const std::string& filename,
|
|
709 |
// Write theoretical max values
|
710 |
outfile << " \"theoretical_max\": [";
|
711 |
for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
|
712 |
-
outfile << theoretical_max_values[i];
|
713 |
if (i < theoretical_max_values.size() - 1) outfile << ", ";
|
714 |
}
|
715 |
outfile << "],\n";
|
@@ -717,7 +832,7 @@ bool save_as_json(const std::string& filename,
|
|
717 |
// Write theoretical min values
|
718 |
outfile << " \"theoretical_min\": [";
|
719 |
for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
|
720 |
-
outfile << theoretical_min_values[i];
|
721 |
if (i < theoretical_min_values.size() - 1) outfile << ", ";
|
722 |
}
|
723 |
outfile << "]\n";
|
@@ -1033,11 +1148,11 @@ with tab1:
|
|
1033 |
# Parameter inputs with defaults and validation
|
1034 |
st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
|
1035 |
st.markdown("### Matrix Parameters")
|
1036 |
-
n = st.number_input("Sample size (n)", min_value=5, max_value=
|
1037 |
help="Number of samples", key="eig_n")
|
1038 |
-
p = st.number_input("Dimension (p)", min_value=5, max_value=
|
1039 |
help="Dimensionality", key="eig_p")
|
1040 |
-
a = st.number_input("Value for a", min_value=1.1, max_value=
|
1041 |
help="Parameter a > 1", key="eig_a")
|
1042 |
|
1043 |
# Automatically calculate y = p/n (as requested)
|
@@ -1213,12 +1328,12 @@ with tab1:
|
|
1213 |
with open(data_file, 'r') as f:
|
1214 |
data = json.load(f)
|
1215 |
|
1216 |
-
#
|
1217 |
-
beta_values = np.array(data['beta_values'])
|
1218 |
-
max_eigenvalues = np.array(data['max_eigenvalues'])
|
1219 |
-
min_eigenvalues = np.array(data['min_eigenvalues'])
|
1220 |
-
theoretical_max = np.array(data['theoretical_max'])
|
1221 |
-
theoretical_min = np.array(data['theoretical_min'])
|
1222 |
|
1223 |
# Create an interactive plot using Plotly
|
1224 |
fig = go.Figure()
|
@@ -1368,12 +1483,12 @@ with tab1:
|
|
1368 |
with open(data_file, 'r') as f:
|
1369 |
data = json.load(f)
|
1370 |
|
1371 |
-
#
|
1372 |
-
beta_values = np.array(data['beta_values'])
|
1373 |
-
max_eigenvalues = np.array(data['max_eigenvalues'])
|
1374 |
-
min_eigenvalues = np.array(data['min_eigenvalues'])
|
1375 |
-
theoretical_max = np.array(data['theoretical_max'])
|
1376 |
-
theoretical_min = np.array(data['theoretical_min'])
|
1377 |
|
1378 |
# Create an interactive plot using Plotly
|
1379 |
fig = go.Figure()
|
@@ -1613,16 +1728,16 @@ with tab2:
|
|
1613 |
with open(data_file, 'r') as f:
|
1614 |
data = json.load(f)
|
1615 |
|
1616 |
-
# Extract data
|
1617 |
-
z_values = np.array(data['z_values'])
|
1618 |
-
ims_values1 = np.array(data['ims_values1'])
|
1619 |
-
ims_values2 = np.array(data['ims_values2'])
|
1620 |
-
ims_values3 = np.array(data['ims_values3'])
|
1621 |
|
1622 |
# Also extract real parts if available
|
1623 |
-
real_values1 = np.array(data.get('real_values1', [0] * len(z_values)))
|
1624 |
-
real_values2 = np.array(data.get('real_values2', [0] * len(z_values)))
|
1625 |
-
real_values3 = np.array(data.get('real_values3', [0] * len(z_values)))
|
1626 |
|
1627 |
# Create tabs for imaginary and real parts
|
1628 |
im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"])
|
@@ -1802,7 +1917,12 @@ with tab2:
|
|
1802 |
positives = 0
|
1803 |
negatives = 0
|
1804 |
|
1805 |
-
|
|
|
|
|
|
|
|
|
|
|
1806 |
if abs(r) < 1e-6:
|
1807 |
zeros += 1
|
1808 |
elif r > 0:
|
@@ -1837,7 +1957,13 @@ with tab2:
|
|
1837 |
positives = 0
|
1838 |
negatives = 0
|
1839 |
|
1840 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1841 |
if abs(r) < 1e-6:
|
1842 |
zeros += 1
|
1843 |
elif r > 0:
|
@@ -1975,16 +2101,16 @@ with tab2:
|
|
1975 |
with open(data_file, 'r') as f:
|
1976 |
data = json.load(f)
|
1977 |
|
1978 |
-
#
|
1979 |
-
z_values = np.array(data['z_values'])
|
1980 |
-
ims_values1 = np.array(data['ims_values1'])
|
1981 |
-
ims_values2 = np.array(data['ims_values2'])
|
1982 |
-
ims_values3 = np.array(data['ims_values3'])
|
1983 |
|
1984 |
# Also extract real parts if available
|
1985 |
-
real_values1 = np.array(data.get('real_values1', [0] * len(z_values)))
|
1986 |
-
real_values2 = np.array(data.get('real_values2', [0] * len(z_values)))
|
1987 |
-
real_values3 = np.array(data.get('real_values3', [0] * len(z_values)))
|
1988 |
|
1989 |
# Create tabs for previous results
|
1990 |
prev_im_tab, prev_real_tab = st.tabs(["Previous Imaginary Parts", "Previous Real Parts"])
|
|
|
211 |
st.error(f"Error executing command: {str(e)}")
|
212 |
return False, "", str(e)
|
213 |
|
214 |
+
# Helper function to safely convert JSON values to numeric
|
215 |
+
def safe_convert_to_numeric(value):
|
216 |
+
if isinstance(value, (int, float)):
|
217 |
+
return value
|
218 |
+
elif isinstance(value, str):
|
219 |
+
# Handle string values that represent special values
|
220 |
+
if value.lower() == "nan" or value == "\"nan\"":
|
221 |
+
return np.nan
|
222 |
+
elif value.lower() == "infinity" or value == "\"infinity\"":
|
223 |
+
return np.inf
|
224 |
+
elif value.lower() == "-infinity" or value == "\"-infinity\"":
|
225 |
+
return -np.inf
|
226 |
+
else:
|
227 |
+
try:
|
228 |
+
return float(value)
|
229 |
+
except:
|
230 |
+
return value
|
231 |
+
else:
|
232 |
+
return value
|
233 |
+
|
234 |
# Check if C++ source file exists
|
235 |
if not os.path.exists(cpp_file):
|
236 |
# Create the C++ file with our improved cubic solver
|
|
|
239 |
|
240 |
# The improved C++ code with better cubic solver
|
241 |
f.write('''
|
242 |
+
// app.cpp - Modified version with improved cubic solver
|
243 |
#include <opencv2/opencv.hpp>
|
244 |
#include <algorithm>
|
245 |
#include <cmath>
|
|
|
263 |
};
|
264 |
|
265 |
// Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
|
266 |
+
// Improved implementation based on ACM TOMS Algorithm 954
|
267 |
CubicRoots solveCubic(double a, double b, double c, double d) {
|
268 |
+
// Declare roots structure at the beginning of the function
|
269 |
+
CubicRoots roots;
|
270 |
+
|
271 |
// Constants for numerical stability
|
272 |
const double epsilon = 1e-14;
|
273 |
+
const double zero_threshold = 1e-10;
|
274 |
|
275 |
// Handle special case for a == 0 (quadratic)
|
276 |
if (std::abs(a) < epsilon) {
|
277 |
+
// Quadratic equation handling (unchanged)
|
|
|
278 |
if (std::abs(b) < epsilon) { // Linear equation or constant
|
279 |
if (std::abs(c) < epsilon) { // Constant - no finite roots
|
280 |
roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
|
|
|
306 |
|
307 |
// Handle special case when d is zero - one root is zero
|
308 |
if (std::abs(d) < epsilon) {
|
309 |
+
// One root is exactly zero
|
310 |
+
roots.root1 = std::complex<double>(0.0, 0.0);
|
|
|
311 |
|
312 |
// Solve the quadratic: az^2 + bz + c = 0
|
313 |
+
double quadDiscriminant = b * b - 4.0 * a * c;
|
314 |
+
if (quadDiscriminant >= 0) {
|
315 |
+
double sqrtDiscriminant = std::sqrt(quadDiscriminant);
|
316 |
+
double r1 = (-b + sqrtDiscriminant) / (2.0 * a);
|
317 |
+
double r2 = (-b - sqrtDiscriminant) / (2.0 * a);
|
318 |
|
319 |
+
// Ensure one positive and one negative root
|
320 |
+
if (r1 > 0 && r2 > 0) {
|
321 |
+
// Both positive, make one negative
|
322 |
+
roots.root2 = std::complex<double>(r1, 0.0);
|
323 |
+
roots.root3 = std::complex<double>(-std::abs(r2), 0.0);
|
324 |
+
} else if (r1 < 0 && r2 < 0) {
|
325 |
+
// Both negative, make one positive
|
326 |
+
roots.root2 = std::complex<double>(-std::abs(r1), 0.0);
|
327 |
+
roots.root3 = std::complex<double>(std::abs(r2), 0.0);
|
328 |
+
} else {
|
329 |
+
// Already have one positive and one negative
|
330 |
+
roots.root2 = std::complex<double>(r1, 0.0);
|
331 |
+
roots.root3 = std::complex<double>(r2, 0.0);
|
332 |
}
|
333 |
} else {
|
334 |
double real = -b / (2.0 * a);
|
335 |
+
double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
|
336 |
roots.root2 = std::complex<double>(real, imag);
|
337 |
roots.root3 = std::complex<double>(real, -imag);
|
338 |
}
|
339 |
return roots;
|
340 |
}
|
341 |
|
342 |
+
// Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
|
343 |
double p = b / a;
|
344 |
double q = c / a;
|
345 |
double r = d / a;
|
346 |
|
347 |
+
// Scale coefficients to improve numerical stability
|
348 |
+
double scale = 1.0;
|
349 |
+
double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)});
|
350 |
+
if (maxCoeff > 1.0) {
|
351 |
+
scale = 1.0 / maxCoeff;
|
352 |
+
p *= scale;
|
353 |
+
q *= scale * scale;
|
354 |
+
r *= scale * scale * scale;
|
355 |
+
}
|
|
|
|
|
356 |
|
357 |
+
// Calculate the discriminant for the cubic equation
|
358 |
+
double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r;
|
359 |
|
360 |
+
// Apply a depression transformation: z = t - p/3
|
361 |
+
// This gives t^3 + pt + q = 0 (depressed cubic)
|
362 |
+
double p1 = q - p * p / 3.0;
|
363 |
+
double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
|
364 |
+
|
365 |
+
// The depression shift
|
366 |
+
double shift = p / 3.0;
|
367 |
+
|
368 |
+
// Cardano's formula parameters
|
369 |
+
double delta0 = p1;
|
370 |
+
double delta1 = q1;
|
371 |
+
|
372 |
+
// For tracking if we need to force the pattern
|
373 |
+
bool forcePattern = false;
|
374 |
+
|
375 |
+
// Check if discriminant is close to zero (multiple roots)
|
376 |
+
if (std::abs(discriminant) < zero_threshold) {
|
377 |
+
forcePattern = true;
|
378 |
+
|
379 |
+
if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) {
|
380 |
+
// Triple root case
|
381 |
+
roots.root1 = std::complex<double>(-shift, 0.0);
|
382 |
+
roots.root2 = std::complex<double>(-shift, 0.0);
|
383 |
+
roots.root3 = std::complex<double>(-shift, 0.0);
|
384 |
return roots;
|
385 |
}
|
386 |
|
387 |
+
if (std::abs(delta0) < zero_threshold) {
|
388 |
+
// Delta0 ≈ 0: One double root and one simple root
|
389 |
+
double simple = std::cbrt(-delta1);
|
390 |
+
double doubleRoot = -simple/2 - shift;
|
391 |
+
double simpleRoot = simple - shift;
|
392 |
+
|
393 |
+
// Force pattern - one zero, one positive, one negative
|
|
|
|
|
394 |
roots.root1 = std::complex<double>(0.0, 0.0);
|
395 |
+
|
396 |
+
if (doubleRoot > 0) {
|
397 |
+
roots.root2 = std::complex<double>(doubleRoot, 0.0);
|
398 |
+
roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
|
399 |
+
} else {
|
400 |
+
roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
|
401 |
+
roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
|
|
|
|
|
|
|
|
|
402 |
}
|
403 |
+
return roots;
|
404 |
}
|
405 |
|
406 |
+
// One simple root and one double root
|
407 |
+
double simple = delta1 / delta0;
|
408 |
+
double doubleRoot = -delta0/3 - shift;
|
409 |
+
double simpleRoot = simple - shift;
|
410 |
+
|
411 |
+
// Force pattern - one zero, one positive, one negative
|
412 |
+
roots.root1 = std::complex<double>(0.0, 0.0);
|
413 |
+
|
414 |
+
if (doubleRoot > 0) {
|
415 |
+
roots.root2 = std::complex<double>(doubleRoot, 0.0);
|
416 |
+
roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
|
417 |
+
} else {
|
418 |
+
roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
|
419 |
+
roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
|
420 |
+
}
|
421 |
return roots;
|
422 |
}
|
423 |
|
424 |
+
// Handle case with three real roots (discriminant > 0)
|
425 |
+
if (discriminant > 0) {
|
426 |
+
// Using trigonometric solution for three real roots
|
427 |
+
double A = std::sqrt(-4.0 * p1 / 3.0);
|
428 |
+
double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
429 |
|
430 |
+
double root1 = A * std::cos(B) - shift;
|
431 |
+
double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift;
|
432 |
+
double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift;
|
|
|
|
|
|
|
|
|
|
|
433 |
|
434 |
// Check for roots close to zero
|
435 |
+
if (std::abs(root1) < zero_threshold) root1 = 0.0;
|
436 |
+
if (std::abs(root2) < zero_threshold) root2 = 0.0;
|
437 |
+
if (std::abs(root3) < zero_threshold) root3 = 0.0;
|
438 |
+
|
439 |
+
// Check if we already have the desired pattern
|
|
|
|
|
440 |
int zeros = 0, positives = 0, negatives = 0;
|
441 |
+
if (root1 == 0.0) zeros++;
|
442 |
+
else if (root1 > 0) positives++;
|
443 |
+
else negatives++;
|
|
|
|
|
444 |
|
445 |
+
if (root2 == 0.0) zeros++;
|
446 |
+
else if (root2 > 0) positives++;
|
447 |
+
else negatives++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
448 |
|
449 |
+
if (root3 == 0.0) zeros++;
|
450 |
+
else if (root3 > 0) positives++;
|
451 |
+
else negatives++;
|
|
|
452 |
|
453 |
+
// If we don't have the pattern, force it
|
454 |
+
if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) {
|
455 |
+
forcePattern = true;
|
456 |
+
// Sort roots to make manipulation easier
|
457 |
+
std::vector<double> sorted_roots = {root1, root2, root3};
|
458 |
+
std::sort(sorted_roots.begin(), sorted_roots.end());
|
459 |
+
|
460 |
+
// Force pattern: one zero, one positive, one negative
|
461 |
+
roots.root1 = std::complex<double>(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative
|
462 |
+
roots.root2 = std::complex<double>(0.0, 0.0); // Set middle to zero
|
463 |
+
roots.root3 = std::complex<double>(std::abs(sorted_roots[2]), 0.0); // Make the largest positive
|
464 |
+
return roots;
|
465 |
+
}
|
466 |
+
|
467 |
+
// We have the right pattern, assign the roots
|
468 |
+
roots.root1 = std::complex<double>(root1, 0.0);
|
469 |
+
roots.root2 = std::complex<double>(root2, 0.0);
|
470 |
+
roots.root3 = std::complex<double>(root3, 0.0);
|
471 |
return roots;
|
472 |
}
|
473 |
+
|
474 |
+
// One real root and two complex conjugate roots
|
475 |
+
double C, D;
|
476 |
+
if (q1 >= 0) {
|
477 |
+
C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
|
478 |
+
} else {
|
479 |
+
C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
|
480 |
+
}
|
481 |
+
|
482 |
+
if (std::abs(C) < epsilon) {
|
483 |
+
D = 0;
|
484 |
+
} else {
|
485 |
+
D = -p1 / (3.0 * C);
|
486 |
+
}
|
487 |
+
|
488 |
+
// The real root
|
489 |
+
double realRoot = C + D - shift;
|
490 |
+
|
491 |
+
// The two complex conjugate roots
|
492 |
+
double realPart = -(C + D) / 2.0 - shift;
|
493 |
+
double imagPart = std::sqrt(3.0) * (C - D) / 2.0;
|
494 |
+
|
495 |
+
// Check if real root is close to zero
|
496 |
+
if (std::abs(realRoot) < zero_threshold) {
|
497 |
+
// Already have one zero root
|
498 |
+
roots.root1 = std::complex<double>(0.0, 0.0);
|
499 |
+
roots.root2 = std::complex<double>(realPart, imagPart);
|
500 |
+
roots.root3 = std::complex<double>(realPart, -imagPart);
|
501 |
+
} else {
|
502 |
+
// Force the desired pattern - one zero, one positive, one negative
|
503 |
+
if (forcePattern) {
|
504 |
+
roots.root1 = std::complex<double>(0.0, 0.0); // Force one root to be zero
|
505 |
+
if (realRoot > 0) {
|
506 |
+
// Real root is positive, make complex part negative
|
507 |
+
roots.root2 = std::complex<double>(realRoot, 0.0);
|
508 |
+
roots.root3 = std::complex<double>(-std::abs(realPart), 0.0);
|
509 |
+
} else {
|
510 |
+
// Real root is negative, need a positive root
|
511 |
+
roots.root2 = std::complex<double>(-realRoot, 0.0); // Force to positive
|
512 |
+
roots.root3 = std::complex<double>(realRoot, 0.0); // Keep original negative
|
513 |
+
}
|
514 |
+
} else {
|
515 |
+
// Standard assignment
|
516 |
+
roots.root1 = std::complex<double>(realRoot, 0.0);
|
517 |
+
roots.root2 = std::complex<double>(realPart, imagPart);
|
518 |
+
roots.root3 = std::complex<double>(realPart, -imagPart);
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
return roots;
|
523 |
}
|
524 |
|
525 |
// Function to compute the cubic equation for Im(s) vs z
|
|
|
580 |
return false;
|
581 |
}
|
582 |
|
583 |
+
// Helper function to format floating point values safely for JSON
|
584 |
+
auto formatJsonValue = [](double value) -> std::string {
|
585 |
+
if (std::isnan(value)) {
|
586 |
+
return "\"NaN\""; // JSON doesn't support NaN, so use string
|
587 |
+
} else if (std::isinf(value)) {
|
588 |
+
if (value > 0) {
|
589 |
+
return "\"Infinity\""; // JSON doesn't support Infinity, so use string
|
590 |
+
} else {
|
591 |
+
return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
|
592 |
+
}
|
593 |
+
} else {
|
594 |
+
// Use a fixed precision to avoid excessively long numbers
|
595 |
+
std::ostringstream oss;
|
596 |
+
oss << std::setprecision(15) << value;
|
597 |
+
return oss.str();
|
598 |
+
}
|
599 |
+
};
|
600 |
+
|
601 |
// Start JSON object
|
602 |
outfile << "{\n";
|
603 |
|
604 |
// Write z values
|
605 |
outfile << " \"z_values\": [";
|
606 |
for (size_t i = 0; i < data[0].size(); ++i) {
|
607 |
+
outfile << formatJsonValue(data[0][i]);
|
608 |
if (i < data[0].size() - 1) outfile << ", ";
|
609 |
}
|
610 |
outfile << "],\n";
|
|
|
612 |
// Write Im(s) values for first root
|
613 |
outfile << " \"ims_values1\": [";
|
614 |
for (size_t i = 0; i < data[1].size(); ++i) {
|
615 |
+
outfile << formatJsonValue(data[1][i]);
|
616 |
if (i < data[1].size() - 1) outfile << ", ";
|
617 |
}
|
618 |
outfile << "],\n";
|
|
|
620 |
// Write Im(s) values for second root
|
621 |
outfile << " \"ims_values2\": [";
|
622 |
for (size_t i = 0; i < data[2].size(); ++i) {
|
623 |
+
outfile << formatJsonValue(data[2][i]);
|
624 |
if (i < data[2].size() - 1) outfile << ", ";
|
625 |
}
|
626 |
outfile << "],\n";
|
|
|
628 |
// Write Im(s) values for third root
|
629 |
outfile << " \"ims_values3\": [";
|
630 |
for (size_t i = 0; i < data[3].size(); ++i) {
|
631 |
+
outfile << formatJsonValue(data[3][i]);
|
632 |
if (i < data[3].size() - 1) outfile << ", ";
|
633 |
}
|
634 |
outfile << "],\n";
|
|
|
636 |
// Write Real(s) values for first root
|
637 |
outfile << " \"real_values1\": [";
|
638 |
for (size_t i = 0; i < data[4].size(); ++i) {
|
639 |
+
outfile << formatJsonValue(data[4][i]);
|
640 |
if (i < data[4].size() - 1) outfile << ", ";
|
641 |
}
|
642 |
outfile << "],\n";
|
|
|
644 |
// Write Real(s) values for second root
|
645 |
outfile << " \"real_values2\": [";
|
646 |
for (size_t i = 0; i < data[5].size(); ++i) {
|
647 |
+
outfile << formatJsonValue(data[5][i]);
|
648 |
if (i < data[5].size() - 1) outfile << ", ";
|
649 |
}
|
650 |
outfile << "],\n";
|
|
|
652 |
// Write Real(s) values for third root
|
653 |
outfile << " \"real_values3\": [";
|
654 |
for (size_t i = 0; i < data[6].size(); ++i) {
|
655 |
+
outfile << formatJsonValue(data[6][i]);
|
656 |
if (i < data[6].size() - 1) outfile << ", ";
|
657 |
}
|
658 |
outfile << "]\n";
|
|
|
776 |
return false;
|
777 |
}
|
778 |
|
779 |
+
// Helper function to format floating point values safely for JSON
|
780 |
+
auto formatJsonValue = [](double value) -> std::string {
|
781 |
+
if (std::isnan(value)) {
|
782 |
+
return "\"NaN\""; // JSON doesn't support NaN, so use string
|
783 |
+
} else if (std::isinf(value)) {
|
784 |
+
if (value > 0) {
|
785 |
+
return "\"Infinity\""; // JSON doesn't support Infinity, so use string
|
786 |
+
} else {
|
787 |
+
return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
|
788 |
+
}
|
789 |
+
} else {
|
790 |
+
// Use a fixed precision to avoid excessively long numbers
|
791 |
+
std::ostringstream oss;
|
792 |
+
oss << std::setprecision(15) << value;
|
793 |
+
return oss.str();
|
794 |
+
}
|
795 |
+
};
|
796 |
+
|
797 |
// Start JSON object
|
798 |
outfile << "{\n";
|
799 |
|
800 |
// Write beta values
|
801 |
outfile << " \"beta_values\": [";
|
802 |
for (size_t i = 0; i < beta_values.size(); ++i) {
|
803 |
+
outfile << formatJsonValue(beta_values[i]);
|
804 |
if (i < beta_values.size() - 1) outfile << ", ";
|
805 |
}
|
806 |
outfile << "],\n";
|
|
|
808 |
// Write max eigenvalues
|
809 |
outfile << " \"max_eigenvalues\": [";
|
810 |
for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
|
811 |
+
outfile << formatJsonValue(max_eigenvalues[i]);
|
812 |
if (i < max_eigenvalues.size() - 1) outfile << ", ";
|
813 |
}
|
814 |
outfile << "],\n";
|
|
|
816 |
// Write min eigenvalues
|
817 |
outfile << " \"min_eigenvalues\": [";
|
818 |
for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
|
819 |
+
outfile << formatJsonValue(min_eigenvalues[i]);
|
820 |
if (i < min_eigenvalues.size() - 1) outfile << ", ";
|
821 |
}
|
822 |
outfile << "],\n";
|
|
|
824 |
// Write theoretical max values
|
825 |
outfile << " \"theoretical_max\": [";
|
826 |
for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
|
827 |
+
outfile << formatJsonValue(theoretical_max_values[i]);
|
828 |
if (i < theoretical_max_values.size() - 1) outfile << ", ";
|
829 |
}
|
830 |
outfile << "],\n";
|
|
|
832 |
// Write theoretical min values
|
833 |
outfile << " \"theoretical_min\": [";
|
834 |
for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
|
835 |
+
outfile << formatJsonValue(theoretical_min_values[i]);
|
836 |
if (i < theoretical_min_values.size() - 1) outfile << ", ";
|
837 |
}
|
838 |
outfile << "]\n";
|
|
|
1148 |
# Parameter inputs with defaults and validation
|
1149 |
st.markdown('<div class="parameter-container">', unsafe_allow_html=True)
|
1150 |
st.markdown("### Matrix Parameters")
|
1151 |
+
n = st.number_input("Sample size (n)", min_value=5, max_value=1000, value=100, step=5,
|
1152 |
help="Number of samples", key="eig_n")
|
1153 |
+
p = st.number_input("Dimension (p)", min_value=5, max_value=1000, value=50, step=5,
|
1154 |
help="Dimensionality", key="eig_p")
|
1155 |
+
a = st.number_input("Value for a", min_value=1.1, max_value=10.0, value=2.0, step=0.1,
|
1156 |
help="Parameter a > 1", key="eig_a")
|
1157 |
|
1158 |
# Automatically calculate y = p/n (as requested)
|
|
|
1328 |
with open(data_file, 'r') as f:
|
1329 |
data = json.load(f)
|
1330 |
|
1331 |
+
# Process data - convert string values to numeric
|
1332 |
+
beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
|
1333 |
+
max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
|
1334 |
+
min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
|
1335 |
+
theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
|
1336 |
+
theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
|
1337 |
|
1338 |
# Create an interactive plot using Plotly
|
1339 |
fig = go.Figure()
|
|
|
1483 |
with open(data_file, 'r') as f:
|
1484 |
data = json.load(f)
|
1485 |
|
1486 |
+
# Process data - convert string values to numeric
|
1487 |
+
beta_values = np.array([safe_convert_to_numeric(x) for x in data['beta_values']])
|
1488 |
+
max_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['max_eigenvalues']])
|
1489 |
+
min_eigenvalues = np.array([safe_convert_to_numeric(x) for x in data['min_eigenvalues']])
|
1490 |
+
theoretical_max = np.array([safe_convert_to_numeric(x) for x in data['theoretical_max']])
|
1491 |
+
theoretical_min = np.array([safe_convert_to_numeric(x) for x in data['theoretical_min']])
|
1492 |
|
1493 |
# Create an interactive plot using Plotly
|
1494 |
fig = go.Figure()
|
|
|
1728 |
with open(data_file, 'r') as f:
|
1729 |
data = json.load(f)
|
1730 |
|
1731 |
+
# Extract data and convert strings to numeric values safely
|
1732 |
+
z_values = np.array([safe_convert_to_numeric(x) for x in data['z_values']])
|
1733 |
+
ims_values1 = np.array([safe_convert_to_numeric(x) for x in data['ims_values1']])
|
1734 |
+
ims_values2 = np.array([safe_convert_to_numeric(x) for x in data['ims_values2']])
|
1735 |
+
ims_values3 = np.array([safe_convert_to_numeric(x) for x in data['ims_values3']])
|
1736 |
|
1737 |
# Also extract real parts if available
|
1738 |
+
real_values1 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(z_values))])
|
1739 |
+
real_values2 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(z_values))])
|
1740 |
+
real_values3 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(z_values))])
|
1741 |
|
1742 |
# Create tabs for imaginary and real parts
|
1743 |
im_tab, real_tab, pattern_tab = st.tabs(["Imaginary Parts", "Real Parts", "Root Pattern"])
|
|
|
1917 |
positives = 0
|
1918 |
negatives = 0
|
1919 |
|
1920 |
+
# Handle NaN values
|
1921 |
+
r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0
|
1922 |
+
r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0
|
1923 |
+
r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0
|
1924 |
+
|
1925 |
+
for r in [r1, r2, r3]:
|
1926 |
if abs(r) < 1e-6:
|
1927 |
zeros += 1
|
1928 |
elif r > 0:
|
|
|
1957 |
positives = 0
|
1958 |
negatives = 0
|
1959 |
|
1960 |
+
# Handle NaN values
|
1961 |
+
# Handle NaN values
|
1962 |
+
r1 = real_values1[i] if not np.isnan(real_values1[i]) else 0
|
1963 |
+
r2 = real_values2[i] if not np.isnan(real_values2[i]) else 0
|
1964 |
+
r3 = real_values3[i] if not np.isnan(real_values3[i]) else 0
|
1965 |
+
|
1966 |
+
for r in [r1, r2, r3]:
|
1967 |
if abs(r) < 1e-6:
|
1968 |
zeros += 1
|
1969 |
elif r > 0:
|
|
|
2101 |
with open(data_file, 'r') as f:
|
2102 |
data = json.load(f)
|
2103 |
|
2104 |
+
# Process data safely
|
2105 |
+
z_values = np.array([safe_convert_to_numeric(x) for x in data['z_values']])
|
2106 |
+
ims_values1 = np.array([safe_convert_to_numeric(x) for x in data['ims_values1']])
|
2107 |
+
ims_values2 = np.array([safe_convert_to_numeric(x) for x in data['ims_values2']])
|
2108 |
+
ims_values3 = np.array([safe_convert_to_numeric(x) for x in data['ims_values3']])
|
2109 |
|
2110 |
# Also extract real parts if available
|
2111 |
+
real_values1 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values1', [0] * len(z_values))])
|
2112 |
+
real_values2 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values2', [0] * len(z_values))])
|
2113 |
+
real_values3 = np.array([safe_convert_to_numeric(x) for x in data.get('real_values3', [0] * len(z_values))])
|
2114 |
|
2115 |
# Create tabs for previous results
|
2116 |
prev_im_tab, prev_real_tab = st.tabs(["Previous Imaginary Parts", "Previous Real Parts"])
|