euler314 commited on
Commit
bf64ee9
·
verified ·
1 Parent(s): ebb13a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +296 -170
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 for command line arguments with improved cubic solver
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 to properly handle cases where roots should be one negative, one positive, one zero
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; // Threshold for considering a value as zero
251
 
252
  // Handle special case for a == 0 (quadratic)
253
  if (std::abs(a) < epsilon) {
254
- CubicRoots roots;
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
- // Factor out z: z(az^2 + bz + c) = 0
288
- CubicRoots roots;
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 discriminant = b * b - 4.0 * a * c;
293
- if (discriminant >= 0) {
294
- double sqrtDiscriminant = std::sqrt(discriminant);
295
- roots.root2 = std::complex<double>((-b + sqrtDiscriminant) / (2.0 * a), 0.0);
296
- roots.root3 = std::complex<double>((-b - sqrtDiscriminant) / (2.0 * a), 0.0);
297
 
298
- // Ensure one positive and one negative root when possible
299
- if (roots.root2.real() > 0 && roots.root3.real() > 0) {
300
- // If both are positive, make the second one negative (arbitrary)
301
- roots.root3 = std::complex<double>(-std::abs(roots.root3.real()), 0.0);
302
- } else if (roots.root2.real() < 0 && roots.root3.real() < 0) {
303
- // If both are negative, make the second one positive (arbitrary)
304
- roots.root3 = std::complex<double>(std::abs(roots.root3.real()), 0.0);
 
 
 
 
 
 
305
  }
306
  } else {
307
  double real = -b / (2.0 * a);
308
- double imag = std::sqrt(-discriminant) / (2.0 * a);
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
- // Substitute z = t - p/3 to get t^3 + pt^2 + qt + r = 0
321
- double p1 = q - p * p / 3.0;
322
- double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
323
-
324
- // Calculate discriminant
325
- double D = q1 * q1 / 4.0 + p1 * p1 * p1 / 27.0;
326
-
327
- // Precompute values
328
- const double two_pi = 2.0 * M_PI;
329
- const double third = 1.0 / 3.0;
330
- const double p_over_3 = p / 3.0;
331
 
332
- CubicRoots roots;
 
333
 
334
- // Handle the special case where the discriminant is close to zero (all real roots, at least two equal)
335
- if (std::abs(D) < zero_threshold) {
336
- // Special case where all roots are zero
337
- if (std::abs(p1) < zero_threshold && std::abs(q1) < zero_threshold) {
338
- roots.root1 = std::complex<double>(-p_over_3, 0.0);
339
- roots.root2 = std::complex<double>(-p_over_3, 0.0);
340
- roots.root3 = std::complex<double>(-p_over_3, 0.0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  return roots;
342
  }
343
 
344
- // General case for D ≈ 0
345
- double u = std::cbrt(-q1 / 2.0); // Real cube root
346
-
347
- roots.root1 = std::complex<double>(2.0 * u - p_over_3, 0.0);
348
- roots.root2 = std::complex<double>(-u - p_over_3, 0.0);
349
- roots.root3 = roots.root2; // Duplicate root
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
- if (std::abs(roots.root2.real()) < zero_threshold) {
355
- roots.root2 = std::complex<double>(0.0, 0.0);
356
- roots.root3 = std::complex<double>(0.0, 0.0);
357
- }
358
-
359
- // Ensure pattern of one negative, one positive, one zero when possible
360
- if (roots.root1.real() != 0.0 && roots.root2.real() != 0.0) {
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
- if (D > 0) { // One real root and two complex conjugate roots
372
- double sqrtD = std::sqrt(D);
373
- double u = std::cbrt(-q1 / 2.0 + sqrtD);
374
- double v = std::cbrt(-q1 / 2.0 - sqrtD);
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
- // Calculate all three real roots
396
- double root1_val = magnitude * std::cos(angle / 3.0) - p_over_3;
397
- double root2_val = magnitude * std::cos((angle + two_pi) / 3.0) - p_over_3;
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
- for (double& val : root_vals) {
406
- if (std::abs(val) < zero_threshold) {
407
- val = 0.0;
408
- }
409
- }
410
-
411
- // Count zeros, positives, and negatives
412
  int zeros = 0, positives = 0, negatives = 0;
413
- for (double val : root_vals) {
414
- if (val == 0.0) zeros++;
415
- else if (val > 0.0) positives++;
416
- else negatives++;
417
- }
418
 
419
- // If we have no zeros but have both positives and negatives, we're good
420
- // If we have zeros and both positives and negatives, we're good
421
- // If we only have one sign and zeros, we need to force one to be the opposite sign
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
- // Assign roots
438
- roots.root1 = std::complex<double>(root_vals[0], 0.0);
439
- roots.root2 = std::complex<double>(root_vals[1], 0.0);
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=10000000, value=100, step=5,
1037
  help="Number of samples", key="eig_n")
1038
- p = st.number_input("Dimension (p)", min_value=5, max_value=10000000, value=50, step=5,
1039
  help="Dimensionality", key="eig_p")
1040
- a = st.number_input("Value for a", min_value=1.1, max_value=100000.0, value=2.0, step=0.1,
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
- # Extract data
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
- # Extract data
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
- for r in [real_values1[i], real_values2[i], real_values3[i]]:
 
 
 
 
 
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
- for r in [real_values1[i], real_values2[i], real_values3[i]]:
 
 
 
 
 
 
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
- # Extract data
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"])