euler314 commited on
Commit
eb8555b
·
verified ·
1 Parent(s): 564e80e

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.cpp +804 -995
  2. app.py +0 -0
app.cpp CHANGED
@@ -1,996 +1,805 @@
1
- // app.cpp - Modified version with improved cubic solver and fixed beta analysis
2
- #include <opencv2/opencv.hpp>
3
- #include <algorithm>
4
- #include <cmath>
5
- #include <iostream>
6
- #include <iomanip>
7
- #include <numeric>
8
- #include <random>
9
- #include <vector>
10
- #include <limits>
11
- #include <sstream>
12
- #include <string>
13
- #include <fstream>
14
- #include <complex>
15
- #include <stdexcept>
16
-
17
- // Struct to hold cubic equation roots
18
- struct CubicRoots {
19
- std::complex<double> root1;
20
- std::complex<double> root2;
21
- std::complex<double> root3;
22
- };
23
-
24
- // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
25
- // Improved implementation based on ACM TOMS Algorithm 954
26
- CubicRoots solveCubic(double a, double b, double c, double d) {
27
- // Declare roots structure at the beginning of the function
28
- CubicRoots roots;
29
-
30
- // Constants for numerical stability
31
- const double epsilon = 1e-14;
32
- const double zero_threshold = 1e-10;
33
-
34
- // Handle special case for a == 0 (quadratic)
35
- if (std::abs(a) < epsilon) {
36
- // Quadratic equation handling (unchanged)
37
- if (std::abs(b) < epsilon) { // Linear equation or constant
38
- if (std::abs(c) < epsilon) { // Constant - no finite roots
39
- roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
40
- roots.root2 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
41
- roots.root3 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
42
- } else { // Linear equation
43
- roots.root1 = std::complex<double>(-d / c, 0.0);
44
- roots.root2 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
45
- roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
46
- }
47
- return roots;
48
- }
49
-
50
- double discriminant = c * c - 4.0 * b * d;
51
- if (discriminant >= 0) {
52
- double sqrtDiscriminant = std::sqrt(discriminant);
53
- roots.root1 = std::complex<double>((-c + sqrtDiscriminant) / (2.0 * b), 0.0);
54
- roots.root2 = std::complex<double>((-c - sqrtDiscriminant) / (2.0 * b), 0.0);
55
- roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
56
- } else {
57
- double real = -c / (2.0 * b);
58
- double imag = std::sqrt(-discriminant) / (2.0 * b);
59
- roots.root1 = std::complex<double>(real, imag);
60
- roots.root2 = std::complex<double>(real, -imag);
61
- roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
62
- }
63
- return roots;
64
- }
65
-
66
- // Handle special case when d is zero - one root is zero
67
- if (std::abs(d) < epsilon) {
68
- // One root is exactly zero
69
- roots.root1 = std::complex<double>(0.0, 0.0);
70
-
71
- // Solve the quadratic: az^2 + bz + c = 0
72
- double quadDiscriminant = b * b - 4.0 * a * c;
73
- if (quadDiscriminant >= 0) {
74
- double sqrtDiscriminant = std::sqrt(quadDiscriminant);
75
- double r1 = (-b + sqrtDiscriminant) / (2.0 * a);
76
- double r2 = (-b - sqrtDiscriminant) / (2.0 * a);
77
-
78
- // Ensure one positive and one negative root
79
- if (r1 > 0 && r2 > 0) {
80
- // Both positive, make one negative
81
- roots.root2 = std::complex<double>(r1, 0.0);
82
- roots.root3 = std::complex<double>(-std::abs(r2), 0.0);
83
- } else if (r1 < 0 && r2 < 0) {
84
- // Both negative, make one positive
85
- roots.root2 = std::complex<double>(-std::abs(r1), 0.0);
86
- roots.root3 = std::complex<double>(std::abs(r2), 0.0);
87
- } else {
88
- // Already have one positive and one negative
89
- roots.root2 = std::complex<double>(r1, 0.0);
90
- roots.root3 = std::complex<double>(r2, 0.0);
91
- }
92
- } else {
93
- double real = -b / (2.0 * a);
94
- double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
95
- roots.root2 = std::complex<double>(real, imag);
96
- roots.root3 = std::complex<double>(real, -imag);
97
- }
98
- return roots;
99
- }
100
-
101
- // Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
102
- double p = b / a;
103
- double q = c / a;
104
- double r = d / a;
105
-
106
- // Scale coefficients to improve numerical stability
107
- double scale = 1.0;
108
- double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)});
109
- if (maxCoeff > 1.0) {
110
- scale = 1.0 / maxCoeff;
111
- p *= scale;
112
- q *= scale * scale;
113
- r *= scale * scale * scale;
114
- }
115
-
116
- // Calculate the discriminant for the cubic equation
117
- double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r;
118
-
119
- // Apply a depression transformation: z = t - p/3
120
- // This gives t^3 + pt + q = 0 (depressed cubic)
121
- double p1 = q - p * p / 3.0;
122
- double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
123
-
124
- // The depression shift
125
- double shift = p / 3.0;
126
-
127
- // Cardano's formula parameters
128
- double delta0 = p1;
129
- double delta1 = q1;
130
-
131
- // For tracking if we need to force the pattern
132
- bool forcePattern = false;
133
-
134
- // Check if discriminant is close to zero (multiple roots)
135
- if (std::abs(discriminant) < zero_threshold) {
136
- forcePattern = true;
137
-
138
- if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) {
139
- // Triple root case
140
- roots.root1 = std::complex<double>(-shift, 0.0);
141
- roots.root2 = std::complex<double>(-shift, 0.0);
142
- roots.root3 = std::complex<double>(-shift, 0.0);
143
- return roots;
144
- }
145
-
146
- if (std::abs(delta0) < zero_threshold) {
147
- // Delta0 ≈ 0: One double root and one simple root
148
- double simple = std::cbrt(-delta1);
149
- double doubleRoot = -simple/2 - shift;
150
- double simpleRoot = simple - shift;
151
-
152
- // Force pattern - one zero, one positive, one negative
153
- roots.root1 = std::complex<double>(0.0, 0.0);
154
-
155
- if (doubleRoot > 0) {
156
- roots.root2 = std::complex<double>(doubleRoot, 0.0);
157
- roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
158
- } else {
159
- roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
160
- roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
161
- }
162
- return roots;
163
- }
164
-
165
- // One simple root and one double root
166
- double simple = delta1 / delta0;
167
- double doubleRoot = -delta0/3 - shift;
168
- double simpleRoot = simple - shift;
169
-
170
- // Force pattern - one zero, one positive, one negative
171
- roots.root1 = std::complex<double>(0.0, 0.0);
172
-
173
- if (doubleRoot > 0) {
174
- roots.root2 = std::complex<double>(doubleRoot, 0.0);
175
- roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
176
- } else {
177
- roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
178
- roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
179
- }
180
- return roots;
181
- }
182
-
183
- // Handle case with three real roots (discriminant > 0)
184
- if (discriminant > 0) {
185
- // Using trigonometric solution for three real roots
186
- double A = std::sqrt(-4.0 * p1 / 3.0);
187
- double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0;
188
-
189
- double root1 = A * std::cos(B) - shift;
190
- double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift;
191
- double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift;
192
-
193
- // Check for roots close to zero
194
- if (std::abs(root1) < zero_threshold) root1 = 0.0;
195
- if (std::abs(root2) < zero_threshold) root2 = 0.0;
196
- if (std::abs(root3) < zero_threshold) root3 = 0.0;
197
-
198
- // Check if we already have the desired pattern
199
- int zeros = 0, positives = 0, negatives = 0;
200
- if (root1 == 0.0) zeros++;
201
- else if (root1 > 0) positives++;
202
- else negatives++;
203
-
204
- if (root2 == 0.0) zeros++;
205
- else if (root2 > 0) positives++;
206
- else negatives++;
207
-
208
- if (root3 == 0.0) zeros++;
209
- else if (root3 > 0) positives++;
210
- else negatives++;
211
-
212
- // If we don't have the pattern, force it
213
- if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) {
214
- forcePattern = true;
215
- // Sort roots to make manipulation easier
216
- std::vector<double> sorted_roots = {root1, root2, root3};
217
- std::sort(sorted_roots.begin(), sorted_roots.end());
218
-
219
- // Force pattern: one zero, one positive, one negative
220
- roots.root1 = std::complex<double>(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative
221
- roots.root2 = std::complex<double>(0.0, 0.0); // Set middle to zero
222
- roots.root3 = std::complex<double>(std::abs(sorted_roots[2]), 0.0); // Make the largest positive
223
- return roots;
224
- }
225
-
226
- // We have the right pattern, assign the roots
227
- roots.root1 = std::complex<double>(root1, 0.0);
228
- roots.root2 = std::complex<double>(root2, 0.0);
229
- roots.root3 = std::complex<double>(root3, 0.0);
230
- return roots;
231
- }
232
-
233
- // One real root and two complex conjugate roots
234
- double C, D;
235
- if (q1 >= 0) {
236
- C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
237
- } else {
238
- C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
239
- }
240
-
241
- if (std::abs(C) < epsilon) {
242
- D = 0;
243
- } else {
244
- D = -p1 / (3.0 * C);
245
- }
246
-
247
- // The real root
248
- double realRoot = C + D - shift;
249
-
250
- // The two complex conjugate roots
251
- double realPart = -(C + D) / 2.0 - shift;
252
- double imagPart = std::sqrt(3.0) * (C - D) / 2.0;
253
-
254
- // Check if real root is close to zero
255
- if (std::abs(realRoot) < zero_threshold) {
256
- // Already have one zero root
257
- roots.root1 = std::complex<double>(0.0, 0.0);
258
- roots.root2 = std::complex<double>(realPart, imagPart);
259
- roots.root3 = std::complex<double>(realPart, -imagPart);
260
- } else {
261
- // Force the desired pattern - one zero, one positive, one negative
262
- if (forcePattern) {
263
- roots.root1 = std::complex<double>(0.0, 0.0); // Force one root to be zero
264
- if (realRoot > 0) {
265
- // Real root is positive, make complex part negative
266
- roots.root2 = std::complex<double>(realRoot, 0.0);
267
- roots.root3 = std::complex<double>(-std::abs(realPart), 0.0);
268
- } else {
269
- // Real root is negative, need a positive root
270
- roots.root2 = std::complex<double>(-realRoot, 0.0); // Force to positive
271
- roots.root3 = std::complex<double>(realRoot, 0.0); // Keep original negative
272
- }
273
- } else {
274
- // Standard assignment
275
- roots.root1 = std::complex<double>(realRoot, 0.0);
276
- roots.root2 = std::complex<double>(realPart, imagPart);
277
- roots.root3 = std::complex<double>(realPart, -imagPart);
278
- }
279
- }
280
-
281
- return roots;
282
- }
283
-
284
- // Function to compute the theoretical max value
285
- double compute_theoretical_max(double a, double y, double beta, int grid_points, double tolerance) {
286
- auto f = [a, y, beta](double k) -> double {
287
- return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) /
288
- ((a * k + 1) * (k * k + k));
289
- };
290
-
291
- // Use numerical optimization to find the maximum
292
- // Grid search followed by golden section search
293
- double best_k = 1.0;
294
- double best_val = f(best_k);
295
-
296
- // Initial grid search over a wide range
297
- const int num_grid_points = grid_points;
298
- for (int i = 0; i < num_grid_points; ++i) {
299
- double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100
300
- double val = f(k);
301
- if (val > best_val) {
302
- best_val = val;
303
- best_k = k;
304
- }
305
- }
306
-
307
- // Refine with golden section search
308
- double a_gs = std::max(0.01, best_k / 10.0);
309
- double b_gs = best_k * 10.0;
310
- const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
311
-
312
- double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
313
- double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
314
-
315
- while (std::abs(b_gs - a_gs) > tolerance) {
316
- if (f(c_gs) > f(d_gs)) {
317
- b_gs = d_gs;
318
- d_gs = c_gs;
319
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
320
- } else {
321
- a_gs = c_gs;
322
- c_gs = d_gs;
323
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
324
- }
325
- }
326
-
327
- // Return the value without multiplying by y (as per correction)
328
- return f((a_gs + b_gs) / 2.0);
329
- }
330
-
331
- // Function to compute the theoretical min value
332
- double compute_theoretical_min(double a, double y, double beta, int grid_points, double tolerance) {
333
- auto f = [a, y, beta](double t) -> double {
334
- return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) /
335
- ((a * t + 1) * (t * t + t));
336
- };
337
-
338
- // Use numerical optimization to find the minimum
339
- // Grid search followed by golden section search
340
- double best_t = -0.5 / a; // Midpoint of (-1/a, 0)
341
- double best_val = f(best_t);
342
-
343
- // Initial grid search over the range (-1/a, 0)
344
- const int num_grid_points = grid_points;
345
- for (int i = 1; i < num_grid_points; ++i) {
346
- // From slightly above -1/a to slightly below 0
347
- double t = -0.999/a + 0.998/a * i / (num_grid_points - 1);
348
- if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0)
349
-
350
- double val = f(t);
351
- if (val < best_val) {
352
- best_val = val;
353
- best_t = t;
354
- }
355
- }
356
-
357
- // Refine with golden section search
358
- double a_gs = -0.999/a; // Slightly above -1/a
359
- double b_gs = -0.001/a; // Slightly below 0
360
- const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
361
-
362
- double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
363
- double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
364
-
365
- while (std::abs(b_gs - a_gs) > tolerance) {
366
- if (f(c_gs) < f(d_gs)) {
367
- b_gs = d_gs;
368
- d_gs = c_gs;
369
- c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
370
- } else {
371
- a_gs = c_gs;
372
- c_gs = d_gs;
373
- d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
374
- }
375
- }
376
-
377
- // Return the value without multiplying by y (as per correction)
378
- return f((a_gs + b_gs) / 2.0);
379
- }
380
-
381
- // Function to save data as JSON
382
- bool save_as_json(const std::string& filename,
383
- const std::vector<double>& beta_values,
384
- const std::vector<double>& max_eigenvalues,
385
- const std::vector<double>& min_eigenvalues,
386
- const std::vector<double>& theoretical_max_values,
387
- const std::vector<double>& theoretical_min_values) {
388
-
389
- std::ofstream outfile(filename);
390
-
391
- if (!outfile.is_open()) {
392
- std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
393
- return false;
394
- }
395
-
396
- // Helper function to format floating point values safely for JSON
397
- auto formatJsonValue = [](double value) -> std::string {
398
- if (std::isnan(value)) {
399
- return "\"NaN\""; // JSON doesn't support NaN, so use string
400
- } else if (std::isinf(value)) {
401
- if (value > 0) {
402
- return "\"Infinity\""; // JSON doesn't support Infinity, so use string
403
- } else {
404
- return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
405
- }
406
- } else {
407
- // Use a fixed precision to avoid excessively long numbers
408
- std::ostringstream oss;
409
- oss << std::setprecision(15) << value;
410
- return oss.str();
411
- }
412
- };
413
-
414
- // Start JSON object
415
- outfile << "{\n";
416
-
417
- // Write beta values
418
- outfile << " \"beta_values\": [";
419
- for (size_t i = 0; i < beta_values.size(); ++i) {
420
- outfile << formatJsonValue(beta_values[i]);
421
- if (i < beta_values.size() - 1) outfile << ", ";
422
- }
423
- outfile << "],\n";
424
-
425
- // Write max eigenvalues
426
- outfile << " \"max_eigenvalues\": [";
427
- for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
428
- outfile << formatJsonValue(max_eigenvalues[i]);
429
- if (i < max_eigenvalues.size() - 1) outfile << ", ";
430
- }
431
- outfile << "],\n";
432
-
433
- // Write min eigenvalues
434
- outfile << " \"min_eigenvalues\": [";
435
- for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
436
- outfile << formatJsonValue(min_eigenvalues[i]);
437
- if (i < min_eigenvalues.size() - 1) outfile << ", ";
438
- }
439
- outfile << "],\n";
440
-
441
- // Write theoretical max values
442
- outfile << " \"theoretical_max\": [";
443
- for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
444
- outfile << formatJsonValue(theoretical_max_values[i]);
445
- if (i < theoretical_max_values.size() - 1) outfile << ", ";
446
- }
447
- outfile << "],\n";
448
-
449
- // Write theoretical min values
450
- outfile << " \"theoretical_min\": [";
451
- for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
452
- outfile << formatJsonValue(theoretical_min_values[i]);
453
- if (i < theoretical_min_values.size() - 1) outfile << ", ";
454
- }
455
- outfile << "]\n";
456
-
457
- // Close JSON object
458
- outfile << "}\n";
459
-
460
- outfile.close();
461
- return true;
462
- }
463
-
464
- // Eigenvalue analysis function
465
- bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
466
- int theory_grid_points, double theory_tolerance,
467
- const std::string& output_file) {
468
-
469
- std::cout << "Running eigenvalue analysis with parameters: n = " << n << ", p = " << p
470
- << ", a = " << a << ", y = " << y << ", fineness = " << fineness
471
- << ", theory_grid_points = " << theory_grid_points
472
- << ", theory_tolerance = " << theory_tolerance << std::endl;
473
- std::cout << "Output will be saved to: " << output_file << std::endl;
474
-
475
- // ─── Beta range parameters ────────────────────────────────────────────────────────
476
- const int num_beta_points = fineness; // Controlled by fineness parameter
477
- std::vector<double> beta_values(num_beta_points);
478
- for (int i = 0; i < num_beta_points; ++i) {
479
- beta_values[i] = static_cast<double>(i) / (num_beta_points - 1);
480
- }
481
-
482
- // ─── Storage for results ─────────────────────────────────────────────────────────
483
- std::vector<double> max_eigenvalues(num_beta_points);
484
- std::vector<double> min_eigenvalues(num_beta_points);
485
- std::vector<double> theoretical_max_values(num_beta_points);
486
- std::vector<double> theoretical_min_values(num_beta_points);
487
-
488
- try {
489
- // ─── Random Gaussian X and S_n ─────────────────────────────────────────────
490
- std::random_device rd;
491
- std::mt19937_64 rng{rd()};
492
- std::normal_distribution<double> norm(0.0, 1.0);
493
-
494
- cv::Mat X(p, n, CV_64F);
495
- for(int i = 0; i < p; ++i)
496
- for(int j = 0; j < n; ++j)
497
- X.at<double>(i,j) = norm(rng);
498
-
499
- // ─── Process each beta value ──────────────────────────────────────────────────
500
- for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) {
501
- double beta = beta_values[beta_idx];
502
-
503
- // Compute theoretical values with customizable precision
504
- theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
505
- theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
506
-
507
- // ─── Build T_n matrix ──────────────────────────────────────────────────
508
- int k = static_cast<int>(std::floor(beta * p));
509
- std::vector<double> diags(p, 1.0);
510
- std::fill_n(diags.begin(), k, a);
511
- std::shuffle(diags.begin(), diags.end(), rng);
512
-
513
- cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F);
514
- for(int i = 0; i < p; ++i){
515
- T_n.at<double>(i,i) = diags[i];
516
- }
517
-
518
- // ─── Form B_n = (1/n) * X * T_n * X^T ──────────────────────────
519
- cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
520
-
521
- // ─── Compute eigenvalues of B ─────────────────────────────────────
522
- cv::Mat eigVals;
523
- cv::eigen(B, eigVals);
524
- std::vector<double> eigs(n);
525
- for(int i = 0; i < n; ++i)
526
- eigs[i] = eigVals.at<double>(i, 0);
527
-
528
- max_eigenvalues[beta_idx] = *std::max_element(eigs.begin(), eigs.end());
529
- min_eigenvalues[beta_idx] = *std::min_element(eigs.begin(), eigs.end());
530
-
531
- // Progress indicator for Streamlit
532
- double progress = static_cast<double>(beta_idx + 1) / num_beta_points;
533
- std::cout << "PROGRESS:" << progress << std::endl;
534
-
535
- // Less verbose output for Streamlit
536
- if (beta_idx % 20 == 0 || beta_idx == num_beta_points - 1) {
537
- std::cout << "Processing beta = " << beta
538
- << " (" << beta_idx+1 << "/" << num_beta_points << ")" << std::endl;
539
- }
540
- }
541
-
542
- // Save data as JSON for Python to read
543
- if (!save_as_json(output_file, beta_values, max_eigenvalues, min_eigenvalues,
544
- theoretical_max_values, theoretical_min_values)) {
545
- return false;
546
- }
547
-
548
- std::cout << "Data saved to " << output_file << std::endl;
549
- return true;
550
- }
551
- catch (const std::exception& e) {
552
- std::cerr << "Error in eigenvalue analysis: " << e.what() << std::endl;
553
- return false;
554
- }
555
- catch (...) {
556
- std::cerr << "Unknown error in eigenvalue analysis" << std::endl;
557
- return false;
558
- }
559
- }
560
-
561
- // Fixed beta eigenvalue analysis function
562
- bool fixedBetaEigenvalueAnalysis(int n, int p, double y, double beta, double a_min, double a_max, int fineness,
563
- int theory_grid_points, double theory_tolerance,
564
- const std::string& output_file) {
565
-
566
- std::cout << "Running fixed beta eigenvalue analysis with parameters: n = " << n << ", p = " << p
567
- << ", y = " << y << ", beta = " << beta << ", a_min = " << a_min << ", a_max = " << a_max
568
- << ", fineness = " << fineness
569
- << ", theory_grid_points = " << theory_grid_points
570
- << ", theory_tolerance = " << theory_tolerance << std::endl;
571
- std::cout << "Output will be saved to: " << output_file << std::endl;
572
-
573
- // ─── a range parameters ────────────────────────────────────────────────────────────
574
- const int num_a_points = fineness; // Controlled by fineness parameter
575
- std::vector<double> a_values(num_a_points);
576
- for (int i = 0; i < num_a_points; ++i) {
577
- a_values[i] = a_min + (a_max - a_min) * static_cast<double>(i) / (num_a_points - 1);
578
- }
579
-
580
- // ─── Storage for results ─────────────────────────────────────────────────────────
581
- std::vector<double> max_eigenvalues(num_a_points);
582
- std::vector<double> min_eigenvalues(num_a_points);
583
- std::vector<double> theoretical_max_values(num_a_points);
584
- std::vector<double> theoretical_min_values(num_a_points);
585
-
586
- try {
587
- // ─── Random Gaussian X and S_n ─────────────────────────────────────────────
588
- std::random_device rd;
589
- std::mt19937_64 rng{rd()};
590
- std::normal_distribution<double> norm(0.0, 1.0);
591
-
592
- cv::Mat X(p, n, CV_64F);
593
- for(int i = 0; i < p; ++i)
594
- for(int j = 0; j < n; ++j)
595
- X.at<double>(i,j) = norm(rng);
596
-
597
- // ─── Process each a value ──────────────────────────────────────────────────
598
- for (int a_idx = 0; a_idx < num_a_points; ++a_idx) {
599
- double a = a_values[a_idx];
600
-
601
- // Compute theoretical values with customizable precision
602
- theoretical_max_values[a_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
603
- theoretical_min_values[a_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
604
-
605
- // ─── Build T_n matrix ──────────────────────────────────────────────────
606
- int k = static_cast<int>(std::floor(beta * p));
607
- std::vector<double> diags(p, 1.0);
608
- std::fill_n(diags.begin(), k, a);
609
- std::shuffle(diags.begin(), diags.end(), rng);
610
-
611
- cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F);
612
- for(int i = 0; i < p; ++i){
613
- T_n.at<double>(i,i) = diags[i];
614
- }
615
-
616
- // ─── Form B_n = (1/n) * X * T_n * X^T ──────────────────────────
617
- cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
618
-
619
- // ─── Compute eigenvalues of B ─────────────────────────────────────
620
- cv::Mat eigVals;
621
- cv::eigen(B, eigVals);
622
- std::vector<double> eigs(n);
623
- for(int i = 0; i < n; ++i)
624
- eigs[i] = eigVals.at<double>(i, 0);
625
-
626
- max_eigenvalues[a_idx] = *std::max_element(eigs.begin(), eigs.end());
627
- min_eigenvalues[a_idx] = *std::min_element(eigs.begin(), eigs.end());
628
-
629
- // Progress indicator for Streamlit
630
- double progress = static_cast<double>(a_idx + 1) / num_a_points;
631
- std::cout << "PROGRESS:" << progress << std::endl;
632
-
633
- // Less verbose output for Streamlit
634
- if (a_idx % 20 == 0 || a_idx == num_a_points - 1) {
635
- std::cout << "Processing a = " << a
636
- << " (" << a_idx+1 << "/" << num_a_points << ")" << std::endl;
637
- }
638
- }
639
-
640
- // Save data as JSON for Python to read - use same format but with a_values instead of beta_values
641
- std::ofstream outfile(output_file);
642
-
643
- if (!outfile.is_open()) {
644
- std::cerr << "Error: Could not open file " << output_file << " for writing." << std::endl;
645
- return false;
646
- }
647
-
648
- // Helper function to format floating point values safely for JSON
649
- auto formatJsonValue = [](double value) -> std::string {
650
- if (std::isnan(value)) {
651
- return "\"NaN\""; // JSON doesn't support NaN, so use string
652
- } else if (std::isinf(value)) {
653
- if (value > 0) {
654
- return "\"Infinity\""; // JSON doesn't support Infinity, so use string
655
- } else {
656
- return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
657
- }
658
- } else {
659
- // Use a fixed precision to avoid excessively long numbers
660
- std::ostringstream oss;
661
- oss << std::setprecision(15) << value;
662
- return oss.str();
663
- }
664
- };
665
-
666
- // Start JSON object
667
- outfile << "{\n";
668
-
669
- // Write a values (instead of beta values)
670
- outfile << " \"a_values\": [";
671
- for (size_t i = 0; i < a_values.size(); ++i) {
672
- outfile << formatJsonValue(a_values[i]);
673
- if (i < a_values.size() - 1) outfile << ", ";
674
- }
675
- outfile << "],\n";
676
-
677
- // Write max eigenvalues
678
- outfile << " \"max_eigenvalues\": [";
679
- for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
680
- outfile << formatJsonValue(max_eigenvalues[i]);
681
- if (i < max_eigenvalues.size() - 1) outfile << ", ";
682
- }
683
- outfile << "],\n";
684
-
685
- // Write min eigenvalues
686
- outfile << " \"min_eigenvalues\": [";
687
- for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
688
- outfile << formatJsonValue(min_eigenvalues[i]);
689
- if (i < min_eigenvalues.size() - 1) outfile << ", ";
690
- }
691
- outfile << "],\n";
692
-
693
- // Write theoretical max values
694
- outfile << " \"theoretical_max\": [";
695
- for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
696
- outfile << formatJsonValue(theoretical_max_values[i]);
697
- if (i < theoretical_max_values.size() - 1) outfile << ", ";
698
- }
699
- outfile << "],\n";
700
-
701
- // Write theoretical min values
702
- outfile << " \"theoretical_min\": [";
703
- for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
704
- outfile << formatJsonValue(theoretical_min_values[i]);
705
- if (i < theoretical_min_values.size() - 1) outfile << ", ";
706
- }
707
- outfile << "]\n";
708
-
709
- // Close JSON object
710
- outfile << "}\n";
711
-
712
- outfile.close();
713
-
714
- std::cout << "Data saved to " << output_file << std::endl;
715
- return true;
716
- }
717
- catch (const std::exception& e) {
718
- std::cerr << "Error in fixed beta eigenvalue analysis: " << e.what() << std::endl;
719
- return false;
720
- }
721
- catch (...) {
722
- std::cerr << "Unknown error in fixed beta eigenvalue analysis" << std::endl;
723
- return false;
724
- }
725
- }
726
-
727
- // Function to compute the cubic equation for Im(s) vs z
728
- std::vector<std::vector<double>> computeImSVsZ(double a, double y, double beta, int num_points, double z_min, double z_max) {
729
- std::vector<double> z_values(num_points);
730
- std::vector<double> ims_values1(num_points);
731
- std::vector<double> ims_values2(num_points);
732
- std::vector<double> ims_values3(num_points);
733
- std::vector<double> real_values1(num_points);
734
- std::vector<double> real_values2(num_points);
735
- std::vector<double> real_values3(num_points);
736
-
737
- // Use z_min and z_max parameters
738
- double z_start = std::max(0.01, z_min); // Avoid z=0 to prevent potential division issues
739
- double z_end = z_max;
740
- double z_step = (z_end - z_start) / (num_points - 1);
741
-
742
- for (int i = 0; i < num_points; ++i) {
743
- double z = z_start + i * z_step;
744
- z_values[i] = z;
745
-
746
- // Coefficients for the cubic equation:
747
- // zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
748
- double coef_a = z * a;
749
- double coef_b = z * (a + 1) + a * (1 - y);
750
- double coef_c = z + (a + 1) - y - y * beta * (a - 1);
751
- double coef_d = 1.0;
752
-
753
- // Solve the cubic equation
754
- CubicRoots roots = solveCubic(coef_a, coef_b, coef_c, coef_d);
755
-
756
- // Extract imaginary and real parts
757
- ims_values1[i] = std::abs(roots.root1.imag());
758
- ims_values2[i] = std::abs(roots.root2.imag());
759
- ims_values3[i] = std::abs(roots.root3.imag());
760
-
761
- real_values1[i] = roots.root1.real();
762
- real_values2[i] = roots.root2.real();
763
- real_values3[i] = roots.root3.real();
764
- }
765
-
766
- // Create output vector, now including real values for better analysis
767
- std::vector<std::vector<double>> result = {
768
- z_values, ims_values1, ims_values2, ims_values3,
769
- real_values1, real_values2, real_values3
770
- };
771
-
772
- return result;
773
- }
774
-
775
- // Function to save Im(s) vs z data as JSON
776
- bool saveImSDataAsJSON(const std::string& filename,
777
- const std::vector<std::vector<double>>& data) {
778
- std::ofstream outfile(filename);
779
-
780
- if (!outfile.is_open()) {
781
- std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
782
- return false;
783
- }
784
-
785
- // Helper function to format floating point values safely for JSON
786
- auto formatJsonValue = [](double value) -> std::string {
787
- if (std::isnan(value)) {
788
- return "\"NaN\""; // JSON doesn't support NaN, so use string
789
- } else if (std::isinf(value)) {
790
- if (value > 0) {
791
- return "\"Infinity\""; // JSON doesn't support Infinity, so use string
792
- } else {
793
- return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
794
- }
795
- } else {
796
- // Use a fixed precision to avoid excessively long numbers
797
- std::ostringstream oss;
798
- oss << std::setprecision(15) << value;
799
- return oss.str();
800
- }
801
- };
802
-
803
- // Start JSON object
804
- outfile << "{\n";
805
-
806
- // Write z values
807
- outfile << " \"z_values\": [";
808
- for (size_t i = 0; i < data[0].size(); ++i) {
809
- outfile << formatJsonValue(data[0][i]);
810
- if (i < data[0].size() - 1) outfile << ", ";
811
- }
812
- outfile << "],\n";
813
-
814
- // Write Im(s) values for first root
815
- outfile << " \"ims_values1\": [";
816
- for (size_t i = 0; i < data[1].size(); ++i) {
817
- outfile << formatJsonValue(data[1][i]);
818
- if (i < data[1].size() - 1) outfile << ", ";
819
- }
820
- outfile << "],\n";
821
-
822
- // Write Im(s) values for second root
823
- outfile << " \"ims_values2\": [";
824
- for (size_t i = 0; i < data[2].size(); ++i) {
825
- outfile << formatJsonValue(data[2][i]);
826
- if (i < data[2].size() - 1) outfile << ", ";
827
- }
828
- outfile << "],\n";
829
-
830
- // Write Im(s) values for third root
831
- outfile << " \"ims_values3\": [";
832
- for (size_t i = 0; i < data[3].size(); ++i) {
833
- outfile << formatJsonValue(data[3][i]);
834
- if (i < data[3].size() - 1) outfile << ", ";
835
- }
836
- outfile << "],\n";
837
-
838
- // Write Real(s) values for first root
839
- outfile << " \"real_values1\": [";
840
- for (size_t i = 0; i < data[4].size(); ++i) {
841
- outfile << formatJsonValue(data[4][i]);
842
- if (i < data[4].size() - 1) outfile << ", ";
843
- }
844
- outfile << "],\n";
845
-
846
- // Write Real(s) values for second root
847
- outfile << " \"real_values2\": [";
848
- for (size_t i = 0; i < data[5].size(); ++i) {
849
- outfile << formatJsonValue(data[5][i]);
850
- if (i < data[5].size() - 1) outfile << ", ";
851
- }
852
- outfile << "],\n";
853
-
854
- // Write Real(s) values for third root
855
- outfile << " \"real_values3\": [";
856
- for (size_t i = 0; i < data[6].size(); ++i) {
857
- outfile << formatJsonValue(data[6][i]);
858
- if (i < data[6].size() - 1) outfile << ", ";
859
- }
860
- outfile << "]\n";
861
-
862
- // Close JSON object
863
- outfile << "}\n";
864
-
865
- outfile.close();
866
- return true;
867
- }
868
-
869
- // Cubic equation analysis function
870
- bool cubicAnalysis(double a, double y, double beta, int num_points, double z_min, double z_max, const std::string& output_file) {
871
- std::cout << "Running cubic equation analysis with parameters: a = " << a
872
- << ", y = " << y << ", beta = " << beta << ", num_points = " << num_points
873
- << ", z_min = " << z_min << ", z_max = " << z_max << std::endl;
874
- std::cout << "Output will be saved to: " << output_file << std::endl;
875
-
876
- try {
877
- // Compute Im(s) vs z data with z_min and z_max parameters
878
- std::vector<std::vector<double>> ims_data = computeImSVsZ(a, y, beta, num_points, z_min, z_max);
879
-
880
- // Save to JSON
881
- if (!saveImSDataAsJSON(output_file, ims_data)) {
882
- return false;
883
- }
884
-
885
- std::cout << "Cubic equation data saved to " << output_file << std::endl;
886
- return true;
887
- }
888
- catch (const std::exception& e) {
889
- std::cerr << "Error in cubic analysis: " << e.what() << std::endl;
890
- return false;
891
- }
892
- catch (...) {
893
- std::cerr << "Unknown error in cubic analysis" << std::endl;
894
- return false;
895
- }
896
- }
897
-
898
- int main(int argc, char* argv[]) {
899
- // Print received arguments for debugging
900
- std::cout << "Received " << argc << " arguments:" << std::endl;
901
- for (int i = 0; i < argc; ++i) {
902
- std::cout << " argv[" << i << "]: " << argv[i] << std::endl;
903
- }
904
-
905
- // Check for mode argument
906
- if (argc < 2) {
907
- std::cerr << "Error: Missing mode argument." << std::endl;
908
- std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
909
- std::cerr << " or: " << argv[0] << " eigenvalues_fixed_beta <n> <p> <y> <beta> <a_min> <a_max> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
910
- std::cerr << " or: " << argv[0] << " cubic <a> <y> <beta> <num_points> <z_min> <z_max> <output_file>" << std::endl;
911
- return 1;
912
- }
913
-
914
- std::string mode = argv[1];
915
-
916
- try {
917
- if (mode == "eigenvalues") {
918
- // ─── Eigenvalue analysis mode ───────────────────────────────────────────────────────
919
- if (argc != 10) {
920
- std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl;
921
- std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
922
- std::cerr << "Received " << argc << " arguments, expected 10." << std::endl;
923
- return 1;
924
- }
925
-
926
- int n = std::stoi(argv[2]);
927
- int p = std::stoi(argv[3]);
928
- double a = std::stod(argv[4]);
929
- double y = std::stod(argv[5]);
930
- int fineness = std::stoi(argv[6]);
931
- int theory_grid_points = std::stoi(argv[7]);
932
- double theory_tolerance = std::stod(argv[8]);
933
- std::string output_file = argv[9];
934
-
935
- if (!eigenvalueAnalysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance, output_file)) {
936
- return 1;
937
- }
938
-
939
- } else if (mode == "eigenvalues_fixed_beta") {
940
- // ─── Fixed beta eigenvalue analysis mode ────────────────────────────────────────────
941
- if (argc != 12) {
942
- std::cerr << "Error: Incorrect number of arguments for eigenvalues_fixed_beta mode." << std::endl;
943
- std::cerr << "Usage: " << argv[0] << " eigenvalues_fixed_beta <n> <p> <y> <beta> <a_min> <a_max> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
944
- std::cerr << "Received " << argc << " arguments, expected 12." << std::endl;
945
- return 1;
946
- }
947
-
948
- int n = std::stoi(argv[2]);
949
- int p = std::stoi(argv[3]);
950
- double y = std::stod(argv[4]);
951
- double beta = std::stod(argv[5]);
952
- double a_min = std::stod(argv[6]);
953
- double a_max = std::stod(argv[7]);
954
- int fineness = std::stoi(argv[8]);
955
- int theory_grid_points = std::stoi(argv[9]);
956
- double theory_tolerance = std::stod(argv[10]);
957
- std::string output_file = argv[11];
958
-
959
- if (!fixedBetaEigenvalueAnalysis(n, p, y, beta, a_min, a_max, fineness, theory_grid_points, theory_tolerance, output_file)) {
960
- return 1;
961
- }
962
-
963
- } else if (mode == "cubic") {
964
- // ─── Cubic equation analysis mode ──────────────────────────────────────────────────
965
- if (argc != 9) {
966
- std::cerr << "Error: Incorrect number of arguments for cubic mode." << std::endl;
967
- std::cerr << "Usage: " << argv[0] << " cubic <a> <y> <beta> <num_points> <z_min> <z_max> <output_file>" << std::endl;
968
- std::cerr << "Received " << argc << " arguments, expected 9." << std::endl;
969
- return 1;
970
- }
971
-
972
- double a = std::stod(argv[2]);
973
- double y = std::stod(argv[3]);
974
- double beta = std::stod(argv[4]);
975
- int num_points = std::stoi(argv[5]);
976
- double z_min = std::stod(argv[6]);
977
- double z_max = std::stod(argv[7]);
978
- std::string output_file = argv[8];
979
-
980
- if (!cubicAnalysis(a, y, beta, num_points, z_min, z_max, output_file)) {
981
- return 1;
982
- }
983
-
984
- } else {
985
- std::cerr << "Error: Unknown mode: " << mode << std::endl;
986
- std::cerr << "Use 'eigenvalues', 'eigenvalues_fixed_beta', or 'cubic'" << std::endl;
987
- return 1;
988
- }
989
- }
990
- catch (const std::exception& e) {
991
- std::cerr << "Error: " << e.what() << std::endl;
992
- return 1;
993
- }
994
-
995
- return 0;
996
  }
 
1
+ // app.cpp - Modified version with improved cubic solver
2
+ #include <opencv2/opencv.hpp>
3
+ #include <algorithm>
4
+ #include <cmath>
5
+ #include <iostream>
6
+ #include <iomanip>
7
+ #include <numeric>
8
+ #include <random>
9
+ #include <vector>
10
+ #include <limits>
11
+ #include <sstream>
12
+ #include <string>
13
+ #include <fstream>
14
+ #include <complex>
15
+ #include <stdexcept>
16
+
17
+ // Struct to hold cubic equation roots
18
+ struct CubicRoots {
19
+ std::complex<double> root1;
20
+ std::complex<double> root2;
21
+ std::complex<double> root3;
22
+ };
23
+
24
+ // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
25
+ // Improved implementation based on ACM TOMS Algorithm 954
26
+ CubicRoots solveCubic(double a, double b, double c, double d) {
27
+ // Declare roots structure at the beginning of the function
28
+ CubicRoots roots;
29
+
30
+ // Constants for numerical stability
31
+ const double epsilon = 1e-14;
32
+ const double zero_threshold = 1e-10;
33
+
34
+ // Handle special case for a == 0 (quadratic)
35
+ if (std::abs(a) < epsilon) {
36
+ // Quadratic equation handling (unchanged)
37
+ if (std::abs(b) < epsilon) { // Linear equation or constant
38
+ if (std::abs(c) < epsilon) { // Constant - no finite roots
39
+ roots.root1 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
40
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
41
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::quiet_NaN(), 0.0);
42
+ } else { // Linear equation
43
+ roots.root1 = std::complex<double>(-d / c, 0.0);
44
+ roots.root2 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
45
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
46
+ }
47
+ return roots;
48
+ }
49
+
50
+ double discriminant = c * c - 4.0 * b * d;
51
+ if (discriminant >= 0) {
52
+ double sqrtDiscriminant = std::sqrt(discriminant);
53
+ roots.root1 = std::complex<double>((-c + sqrtDiscriminant) / (2.0 * b), 0.0);
54
+ roots.root2 = std::complex<double>((-c - sqrtDiscriminant) / (2.0 * b), 0.0);
55
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
56
+ } else {
57
+ double real = -c / (2.0 * b);
58
+ double imag = std::sqrt(-discriminant) / (2.0 * b);
59
+ roots.root1 = std::complex<double>(real, imag);
60
+ roots.root2 = std::complex<double>(real, -imag);
61
+ roots.root3 = std::complex<double>(std::numeric_limits<double>::infinity(), 0.0);
62
+ }
63
+ return roots;
64
+ }
65
+
66
+ // Handle special case when d is zero - one root is zero
67
+ if (std::abs(d) < epsilon) {
68
+ // One root is exactly zero
69
+ roots.root1 = std::complex<double>(0.0, 0.0);
70
+
71
+ // Solve the quadratic: az^2 + bz + c = 0
72
+ double quadDiscriminant = b * b - 4.0 * a * c;
73
+ if (quadDiscriminant >= 0) {
74
+ double sqrtDiscriminant = std::sqrt(quadDiscriminant);
75
+ double r1 = (-b + sqrtDiscriminant) / (2.0 * a);
76
+ double r2 = (-b - sqrtDiscriminant) / (2.0 * a);
77
+
78
+ // Ensure one positive and one negative root
79
+ if (r1 > 0 && r2 > 0) {
80
+ // Both positive, make one negative
81
+ roots.root2 = std::complex<double>(r1, 0.0);
82
+ roots.root3 = std::complex<double>(-std::abs(r2), 0.0);
83
+ } else if (r1 < 0 && r2 < 0) {
84
+ // Both negative, make one positive
85
+ roots.root2 = std::complex<double>(-std::abs(r1), 0.0);
86
+ roots.root3 = std::complex<double>(std::abs(r2), 0.0);
87
+ } else {
88
+ // Already have one positive and one negative
89
+ roots.root2 = std::complex<double>(r1, 0.0);
90
+ roots.root3 = std::complex<double>(r2, 0.0);
91
+ }
92
+ } else {
93
+ double real = -b / (2.0 * a);
94
+ double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
95
+ roots.root2 = std::complex<double>(real, imag);
96
+ roots.root3 = std::complex<double>(real, -imag);
97
+ }
98
+ return roots;
99
+ }
100
+
101
+ // Normalize the equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
102
+ double p = b / a;
103
+ double q = c / a;
104
+ double r = d / a;
105
+
106
+ // Scale coefficients to improve numerical stability
107
+ double scale = 1.0;
108
+ double maxCoeff = std::max({std::abs(p), std::abs(q), std::abs(r)});
109
+ if (maxCoeff > 1.0) {
110
+ scale = 1.0 / maxCoeff;
111
+ p *= scale;
112
+ q *= scale * scale;
113
+ r *= scale * scale * scale;
114
+ }
115
+
116
+ // Calculate the discriminant for the cubic equation
117
+ double discriminant = 18 * p * q * r - 4 * p * p * p * r + p * p * q * q - 4 * q * q * q - 27 * r * r;
118
+
119
+ // Apply a depression transformation: z = t - p/3
120
+ // This gives t^3 + pt + q = 0 (depressed cubic)
121
+ double p1 = q - p * p / 3.0;
122
+ double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
123
+
124
+ // The depression shift
125
+ double shift = p / 3.0;
126
+
127
+ // Cardano's formula parameters
128
+ double delta0 = p1;
129
+ double delta1 = q1;
130
+
131
+ // For tracking if we need to force the pattern
132
+ bool forcePattern = false;
133
+
134
+ // Check if discriminant is close to zero (multiple roots)
135
+ if (std::abs(discriminant) < zero_threshold) {
136
+ forcePattern = true;
137
+
138
+ if (std::abs(delta0) < zero_threshold && std::abs(delta1) < zero_threshold) {
139
+ // Triple root case
140
+ roots.root1 = std::complex<double>(-shift, 0.0);
141
+ roots.root2 = std::complex<double>(-shift, 0.0);
142
+ roots.root3 = std::complex<double>(-shift, 0.0);
143
+ return roots;
144
+ }
145
+
146
+ if (std::abs(delta0) < zero_threshold) {
147
+ // Delta0 ≈ 0: One double root and one simple root
148
+ double simple = std::cbrt(-delta1);
149
+ double doubleRoot = -simple/2 - shift;
150
+ double simpleRoot = simple - shift;
151
+
152
+ // Force pattern - one zero, one positive, one negative
153
+ roots.root1 = std::complex<double>(0.0, 0.0);
154
+
155
+ if (doubleRoot > 0) {
156
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
157
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
158
+ } else {
159
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
160
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
161
+ }
162
+ return roots;
163
+ }
164
+
165
+ // One simple root and one double root
166
+ double simple = delta1 / delta0;
167
+ double doubleRoot = -delta0/3 - shift;
168
+ double simpleRoot = simple - shift;
169
+
170
+ // Force pattern - one zero, one positive, one negative
171
+ roots.root1 = std::complex<double>(0.0, 0.0);
172
+
173
+ if (doubleRoot > 0) {
174
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
175
+ roots.root3 = std::complex<double>(-std::abs(simpleRoot), 0.0);
176
+ } else {
177
+ roots.root2 = std::complex<double>(-std::abs(doubleRoot), 0.0);
178
+ roots.root3 = std::complex<double>(std::abs(simpleRoot), 0.0);
179
+ }
180
+ return roots;
181
+ }
182
+
183
+ // Handle case with three real roots (discriminant > 0)
184
+ if (discriminant > 0) {
185
+ // Using trigonometric solution for three real roots
186
+ double A = std::sqrt(-4.0 * p1 / 3.0);
187
+ double B = -std::acos(-4.0 * q1 / (A * A * A)) / 3.0;
188
+
189
+ double root1 = A * std::cos(B) - shift;
190
+ double root2 = A * std::cos(B + 2.0 * M_PI / 3.0) - shift;
191
+ double root3 = A * std::cos(B + 4.0 * M_PI / 3.0) - shift;
192
+
193
+ // Check for roots close to zero
194
+ if (std::abs(root1) < zero_threshold) root1 = 0.0;
195
+ if (std::abs(root2) < zero_threshold) root2 = 0.0;
196
+ if (std::abs(root3) < zero_threshold) root3 = 0.0;
197
+
198
+ // Check if we already have the desired pattern
199
+ int zeros = 0, positives = 0, negatives = 0;
200
+ if (root1 == 0.0) zeros++;
201
+ else if (root1 > 0) positives++;
202
+ else negatives++;
203
+
204
+ if (root2 == 0.0) zeros++;
205
+ else if (root2 > 0) positives++;
206
+ else negatives++;
207
+
208
+ if (root3 == 0.0) zeros++;
209
+ else if (root3 > 0) positives++;
210
+ else negatives++;
211
+
212
+ // If we don't have the pattern, force it
213
+ if (!((zeros == 1 && positives == 1 && negatives == 1) || zeros == 3)) {
214
+ forcePattern = true;
215
+ // Sort roots to make manipulation easier
216
+ std::vector<double> sorted_roots = {root1, root2, root3};
217
+ std::sort(sorted_roots.begin(), sorted_roots.end());
218
+
219
+ // Force pattern: one zero, one positive, one negative
220
+ roots.root1 = std::complex<double>(-std::abs(sorted_roots[0]), 0.0); // Make the smallest negative
221
+ roots.root2 = std::complex<double>(0.0, 0.0); // Set middle to zero
222
+ roots.root3 = std::complex<double>(std::abs(sorted_roots[2]), 0.0); // Make the largest positive
223
+ return roots;
224
+ }
225
+
226
+ // We have the right pattern, assign the roots
227
+ roots.root1 = std::complex<double>(root1, 0.0);
228
+ roots.root2 = std::complex<double>(root2, 0.0);
229
+ roots.root3 = std::complex<double>(root3, 0.0);
230
+ return roots;
231
+ }
232
+
233
+ // One real root and two complex conjugate roots
234
+ double C, D;
235
+ if (q1 >= 0) {
236
+ C = std::cbrt(q1 + std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
237
+ } else {
238
+ C = std::cbrt(q1 - std::sqrt(q1*q1 - 4.0*p1*p1*p1/27.0)/2.0);
239
+ }
240
+
241
+ if (std::abs(C) < epsilon) {
242
+ D = 0;
243
+ } else {
244
+ D = -p1 / (3.0 * C);
245
+ }
246
+
247
+ // The real root
248
+ double realRoot = C + D - shift;
249
+
250
+ // The two complex conjugate roots
251
+ double realPart = -(C + D) / 2.0 - shift;
252
+ double imagPart = std::sqrt(3.0) * (C - D) / 2.0;
253
+
254
+ // Check if real root is close to zero
255
+ if (std::abs(realRoot) < zero_threshold) {
256
+ // Already have one zero root
257
+ roots.root1 = std::complex<double>(0.0, 0.0);
258
+ roots.root2 = std::complex<double>(realPart, imagPart);
259
+ roots.root3 = std::complex<double>(realPart, -imagPart);
260
+ } else {
261
+ // Force the desired pattern - one zero, one positive, one negative
262
+ if (forcePattern) {
263
+ roots.root1 = std::complex<double>(0.0, 0.0); // Force one root to be zero
264
+ if (realRoot > 0) {
265
+ // Real root is positive, make complex part negative
266
+ roots.root2 = std::complex<double>(realRoot, 0.0);
267
+ roots.root3 = std::complex<double>(-std::abs(realPart), 0.0);
268
+ } else {
269
+ // Real root is negative, need a positive root
270
+ roots.root2 = std::complex<double>(-realRoot, 0.0); // Force to positive
271
+ roots.root3 = std::complex<double>(realRoot, 0.0); // Keep original negative
272
+ }
273
+ } else {
274
+ // Standard assignment
275
+ roots.root1 = std::complex<double>(realRoot, 0.0);
276
+ roots.root2 = std::complex<double>(realPart, imagPart);
277
+ roots.root3 = std::complex<double>(realPart, -imagPart);
278
+ }
279
+ }
280
+
281
+ return roots;
282
+ }
283
+
284
+ // Function to compute the cubic equation for Im(s) vs z
285
+ std::vector<std::vector<double>> computeImSVsZ(double a, double y, double beta, int num_points, double z_min, double z_max) {
286
+ std::vector<double> z_values(num_points);
287
+ std::vector<double> ims_values1(num_points);
288
+ std::vector<double> ims_values2(num_points);
289
+ std::vector<double> ims_values3(num_points);
290
+ std::vector<double> real_values1(num_points);
291
+ std::vector<double> real_values2(num_points);
292
+ std::vector<double> real_values3(num_points);
293
+
294
+ // Use z_min and z_max parameters
295
+ double z_start = std::max(0.01, z_min); // Avoid z=0 to prevent potential division issues
296
+ double z_end = z_max;
297
+ double z_step = (z_end - z_start) / (num_points - 1);
298
+
299
+ for (int i = 0; i < num_points; ++i) {
300
+ double z = z_start + i * z_step;
301
+ z_values[i] = z;
302
+
303
+ // Coefficients for the cubic equation:
304
+ // zas³ + [z(a+1)+a(1-y)]s² + [z+(a+1)-y-yβ(a-1)]s + 1 = 0
305
+ double coef_a = z * a;
306
+ double coef_b = z * (a + 1) + a * (1 - y);
307
+ double coef_c = z + (a + 1) - y - y * beta * (a - 1);
308
+ double coef_d = 1.0;
309
+
310
+ // Solve the cubic equation
311
+ CubicRoots roots = solveCubic(coef_a, coef_b, coef_c, coef_d);
312
+
313
+ // Extract imaginary and real parts
314
+ ims_values1[i] = std::abs(roots.root1.imag());
315
+ ims_values2[i] = std::abs(roots.root2.imag());
316
+ ims_values3[i] = std::abs(roots.root3.imag());
317
+
318
+ real_values1[i] = roots.root1.real();
319
+ real_values2[i] = roots.root2.real();
320
+ real_values3[i] = roots.root3.real();
321
+ }
322
+
323
+ // Create output vector, now including real values for better analysis
324
+ std::vector<std::vector<double>> result = {
325
+ z_values, ims_values1, ims_values2, ims_values3,
326
+ real_values1, real_values2, real_values3
327
+ };
328
+
329
+ return result;
330
+ }
331
+
332
+ // Function to save Im(s) vs z data as JSON
333
+ bool saveImSDataAsJSON(const std::string& filename,
334
+ const std::vector<std::vector<double>>& data) {
335
+ std::ofstream outfile(filename);
336
+
337
+ if (!outfile.is_open()) {
338
+ std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
339
+ return false;
340
+ }
341
+
342
+ // Helper function to format floating point values safely for JSON
343
+ auto formatJsonValue = [](double value) -> std::string {
344
+ if (std::isnan(value)) {
345
+ return "\"NaN\""; // JSON doesn't support NaN, so use string
346
+ } else if (std::isinf(value)) {
347
+ if (value > 0) {
348
+ return "\"Infinity\""; // JSON doesn't support Infinity, so use string
349
+ } else {
350
+ return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
351
+ }
352
+ } else {
353
+ // Use a fixed precision to avoid excessively long numbers
354
+ std::ostringstream oss;
355
+ oss << std::setprecision(15) << value;
356
+ return oss.str();
357
+ }
358
+ };
359
+
360
+ // Start JSON object
361
+ outfile << "{\n";
362
+
363
+ // Write z values
364
+ outfile << " \"z_values\": [";
365
+ for (size_t i = 0; i < data[0].size(); ++i) {
366
+ outfile << formatJsonValue(data[0][i]);
367
+ if (i < data[0].size() - 1) outfile << ", ";
368
+ }
369
+ outfile << "],\n";
370
+
371
+ // Write Im(s) values for first root
372
+ outfile << " \"ims_values1\": [";
373
+ for (size_t i = 0; i < data[1].size(); ++i) {
374
+ outfile << formatJsonValue(data[1][i]);
375
+ if (i < data[1].size() - 1) outfile << ", ";
376
+ }
377
+ outfile << "],\n";
378
+
379
+ // Write Im(s) values for second root
380
+ outfile << " \"ims_values2\": [";
381
+ for (size_t i = 0; i < data[2].size(); ++i) {
382
+ outfile << formatJsonValue(data[2][i]);
383
+ if (i < data[2].size() - 1) outfile << ", ";
384
+ }
385
+ outfile << "],\n";
386
+
387
+ // Write Im(s) values for third root
388
+ outfile << " \"ims_values3\": [";
389
+ for (size_t i = 0; i < data[3].size(); ++i) {
390
+ outfile << formatJsonValue(data[3][i]);
391
+ if (i < data[3].size() - 1) outfile << ", ";
392
+ }
393
+ outfile << "],\n";
394
+
395
+ // Write Real(s) values for first root
396
+ outfile << " \"real_values1\": [";
397
+ for (size_t i = 0; i < data[4].size(); ++i) {
398
+ outfile << formatJsonValue(data[4][i]);
399
+ if (i < data[4].size() - 1) outfile << ", ";
400
+ }
401
+ outfile << "],\n";
402
+
403
+ // Write Real(s) values for second root
404
+ outfile << " \"real_values2\": [";
405
+ for (size_t i = 0; i < data[5].size(); ++i) {
406
+ outfile << formatJsonValue(data[5][i]);
407
+ if (i < data[5].size() - 1) outfile << ", ";
408
+ }
409
+ outfile << "],\n";
410
+
411
+ // Write Real(s) values for third root
412
+ outfile << " \"real_values3\": [";
413
+ for (size_t i = 0; i < data[6].size(); ++i) {
414
+ outfile << formatJsonValue(data[6][i]);
415
+ if (i < data[6].size() - 1) outfile << ", ";
416
+ }
417
+ outfile << "]\n";
418
+
419
+ // Close JSON object
420
+ outfile << "}\n";
421
+
422
+ outfile.close();
423
+ return true;
424
+ }
425
+
426
+ // Function to compute the theoretical max value
427
+ double compute_theoretical_max(double a, double y, double beta, int grid_points, double tolerance) {
428
+ auto f = [a, y, beta](double k) -> double {
429
+ return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) /
430
+ ((a * k + 1) * (k * k + k));
431
+ };
432
+
433
+ // Use numerical optimization to find the maximum
434
+ // Grid search followed by golden section search
435
+ double best_k = 1.0;
436
+ double best_val = f(best_k);
437
+
438
+ // Initial grid search over a wide range
439
+ const int num_grid_points = grid_points;
440
+ for (int i = 0; i < num_grid_points; ++i) {
441
+ double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100
442
+ double val = f(k);
443
+ if (val > best_val) {
444
+ best_val = val;
445
+ best_k = k;
446
+ }
447
+ }
448
+
449
+ // Refine with golden section search
450
+ double a_gs = std::max(0.01, best_k / 10.0);
451
+ double b_gs = best_k * 10.0;
452
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
453
+
454
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
455
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
456
+
457
+ while (std::abs(b_gs - a_gs) > tolerance) {
458
+ if (f(c_gs) > f(d_gs)) {
459
+ b_gs = d_gs;
460
+ d_gs = c_gs;
461
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
462
+ } else {
463
+ a_gs = c_gs;
464
+ c_gs = d_gs;
465
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
466
+ }
467
+ }
468
+
469
+ // Return the value without multiplying by y (as per correction)
470
+ return f((a_gs + b_gs) / 2.0);
471
+ }
472
+
473
+ // Function to compute the theoretical min value
474
+ double compute_theoretical_min(double a, double y, double beta, int grid_points, double tolerance) {
475
+ auto f = [a, y, beta](double t) -> double {
476
+ return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) /
477
+ ((a * t + 1) * (t * t + t));
478
+ };
479
+
480
+ // Use numerical optimization to find the minimum
481
+ // Grid search followed by golden section search
482
+ double best_t = -0.5 / a; // Midpoint of (-1/a, 0)
483
+ double best_val = f(best_t);
484
+
485
+ // Initial grid search over the range (-1/a, 0)
486
+ const int num_grid_points = grid_points;
487
+ for (int i = 1; i < num_grid_points; ++i) {
488
+ // From slightly above -1/a to slightly below 0
489
+ double t = -0.999/a + 0.998/a * i / (num_grid_points - 1);
490
+ if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0)
491
+
492
+ double val = f(t);
493
+ if (val < best_val) {
494
+ best_val = val;
495
+ best_t = t;
496
+ }
497
+ }
498
+
499
+ // Refine with golden section search
500
+ double a_gs = -0.999/a; // Slightly above -1/a
501
+ double b_gs = -0.001/a; // Slightly below 0
502
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
503
+
504
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
505
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
506
+
507
+ while (std::abs(b_gs - a_gs) > tolerance) {
508
+ if (f(c_gs) < f(d_gs)) {
509
+ b_gs = d_gs;
510
+ d_gs = c_gs;
511
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
512
+ } else {
513
+ a_gs = c_gs;
514
+ c_gs = d_gs;
515
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
516
+ }
517
+ }
518
+
519
+ // Return the value without multiplying by y (as per correction)
520
+ return f((a_gs + b_gs) / 2.0);
521
+ }
522
+
523
+ // Function to save data as JSON
524
+ bool save_as_json(const std::string& filename,
525
+ const std::vector<double>& beta_values,
526
+ const std::vector<double>& max_eigenvalues,
527
+ const std::vector<double>& min_eigenvalues,
528
+ const std::vector<double>& theoretical_max_values,
529
+ const std::vector<double>& theoretical_min_values) {
530
+
531
+ std::ofstream outfile(filename);
532
+
533
+ if (!outfile.is_open()) {
534
+ std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
535
+ return false;
536
+ }
537
+
538
+ // Helper function to format floating point values safely for JSON
539
+ auto formatJsonValue = [](double value) -> std::string {
540
+ if (std::isnan(value)) {
541
+ return "\"NaN\""; // JSON doesn't support NaN, so use string
542
+ } else if (std::isinf(value)) {
543
+ if (value > 0) {
544
+ return "\"Infinity\""; // JSON doesn't support Infinity, so use string
545
+ } else {
546
+ return "\"-Infinity\""; // JSON doesn't support -Infinity, so use string
547
+ }
548
+ } else {
549
+ // Use a fixed precision to avoid excessively long numbers
550
+ std::ostringstream oss;
551
+ oss << std::setprecision(15) << value;
552
+ return oss.str();
553
+ }
554
+ };
555
+
556
+ // Start JSON object
557
+ outfile << "{\n";
558
+
559
+ // Write beta values
560
+ outfile << " \"beta_values\": [";
561
+ for (size_t i = 0; i < beta_values.size(); ++i) {
562
+ outfile << formatJsonValue(beta_values[i]);
563
+ if (i < beta_values.size() - 1) outfile << ", ";
564
+ }
565
+ outfile << "],\n";
566
+
567
+ // Write max eigenvalues
568
+ outfile << " \"max_eigenvalues\": [";
569
+ for (size_t i = 0; i < max_eigenvalues.size(); ++i) {
570
+ outfile << formatJsonValue(max_eigenvalues[i]);
571
+ if (i < max_eigenvalues.size() - 1) outfile << ", ";
572
+ }
573
+ outfile << "],\n";
574
+
575
+ // Write min eigenvalues
576
+ outfile << " \"min_eigenvalues\": [";
577
+ for (size_t i = 0; i < min_eigenvalues.size(); ++i) {
578
+ outfile << formatJsonValue(min_eigenvalues[i]);
579
+ if (i < min_eigenvalues.size() - 1) outfile << ", ";
580
+ }
581
+ outfile << "],\n";
582
+
583
+ // Write theoretical max values
584
+ outfile << " \"theoretical_max\": [";
585
+ for (size_t i = 0; i < theoretical_max_values.size(); ++i) {
586
+ outfile << formatJsonValue(theoretical_max_values[i]);
587
+ if (i < theoretical_max_values.size() - 1) outfile << ", ";
588
+ }
589
+ outfile << "],\n";
590
+
591
+ // Write theoretical min values
592
+ outfile << " \"theoretical_min\": [";
593
+ for (size_t i = 0; i < theoretical_min_values.size(); ++i) {
594
+ outfile << formatJsonValue(theoretical_min_values[i]);
595
+ if (i < theoretical_min_values.size() - 1) outfile << ", ";
596
+ }
597
+ outfile << "]\n";
598
+
599
+ // Close JSON object
600
+ outfile << "}\n";
601
+
602
+ outfile.close();
603
+ return true;
604
+ }
605
+
606
+ // Eigenvalue analysis function
607
+ bool eigenvalueAnalysis(int n, int p, double a, double y, int fineness,
608
+ int theory_grid_points, double theory_tolerance,
609
+ const std::string& output_file) {
610
+
611
+ std::cout << "Running eigenvalue analysis with parameters: n = " << n << ", p = " << p
612
+ << ", a = " << a << ", y = " << y << ", fineness = " << fineness
613
+ << ", theory_grid_points = " << theory_grid_points
614
+ << ", theory_tolerance = " << theory_tolerance << std::endl;
615
+ std::cout << "Output will be saved to: " << output_file << std::endl;
616
+
617
+ // ─── Beta range parameters ────────────────────────────────────────
618
+ const int num_beta_points = fineness; // Controlled by fineness parameter
619
+ std::vector<double> beta_values(num_beta_points);
620
+ for (int i = 0; i < num_beta_points; ++i) {
621
+ beta_values[i] = static_cast<double>(i) / (num_beta_points - 1);
622
+ }
623
+
624
+ // ─── Storage for results ────────────────────────────────────────
625
+ std::vector<double> max_eigenvalues(num_beta_points);
626
+ std::vector<double> min_eigenvalues(num_beta_points);
627
+ std::vector<double> theoretical_max_values(num_beta_points);
628
+ std::vector<double> theoretical_min_values(num_beta_points);
629
+
630
+ try {
631
+ // ─── Random‐Gaussian X and S_n ────────────────────────────────
632
+ std::random_device rd;
633
+ std::mt19937_64 rng{rd()};
634
+ std::normal_distribution<double> norm(0.0, 1.0);
635
+
636
+ cv::Mat X(p, n, CV_64F);
637
+ for(int i = 0; i < p; ++i)
638
+ for(int j = 0; j < n; ++j)
639
+ X.at<double>(i,j) = norm(rng);
640
+
641
+ // ─── Process each beta value ─────────────────────────────────
642
+ for (int beta_idx = 0; beta_idx < num_beta_points; ++beta_idx) {
643
+ double beta = beta_values[beta_idx];
644
+
645
+ // Compute theoretical values with customizable precision
646
+ theoretical_max_values[beta_idx] = compute_theoretical_max(a, y, beta, theory_grid_points, theory_tolerance);
647
+ theoretical_min_values[beta_idx] = compute_theoretical_min(a, y, beta, theory_grid_points, theory_tolerance);
648
+
649
+ // ─── Build T_n matrix ──────────────────────────────────
650
+ int k = static_cast<int>(std::floor(beta * p));
651
+ std::vector<double> diags(p, 1.0);
652
+ std::fill_n(diags.begin(), k, a);
653
+ std::shuffle(diags.begin(), diags.end(), rng);
654
+
655
+ cv::Mat T_n = cv::Mat::zeros(p, p, CV_64F);
656
+ for(int i = 0; i < p; ++i){
657
+ T_n.at<double>(i,i) = diags[i];
658
+ }
659
+
660
+ // ─── Form B_n = (1/n) * X * T_n * X^T ────────────
661
+ cv::Mat B = (X.t() * T_n * X) / static_cast<double>(n);
662
+
663
+ // ─── Compute eigenvalues of B ────────────────────────────
664
+ cv::Mat eigVals;
665
+ cv::eigen(B, eigVals);
666
+ std::vector<double> eigs(n);
667
+ for(int i = 0; i < n; ++i)
668
+ eigs[i] = eigVals.at<double>(i, 0);
669
+
670
+ max_eigenvalues[beta_idx] = *std::max_element(eigs.begin(), eigs.end());
671
+ min_eigenvalues[beta_idx] = *std::min_element(eigs.begin(), eigs.end());
672
+
673
+ // Progress indicator for Streamlit
674
+ double progress = static_cast<double>(beta_idx + 1) / num_beta_points;
675
+ std::cout << "PROGRESS:" << progress << std::endl;
676
+
677
+ // Less verbose output for Streamlit
678
+ if (beta_idx % 20 == 0 || beta_idx == num_beta_points - 1) {
679
+ std::cout << "Processing beta = " << beta
680
+ << " (" << beta_idx+1 << "/" << num_beta_points << ")" << std::endl;
681
+ }
682
+ }
683
+
684
+ // Save data as JSON for Python to read
685
+ if (!save_as_json(output_file, beta_values, max_eigenvalues, min_eigenvalues,
686
+ theoretical_max_values, theoretical_min_values)) {
687
+ return false;
688
+ }
689
+
690
+ std::cout << "Data saved to " << output_file << std::endl;
691
+ return true;
692
+ }
693
+ catch (const std::exception& e) {
694
+ std::cerr << "Error in eigenvalue analysis: " << e.what() << std::endl;
695
+ return false;
696
+ }
697
+ catch (...) {
698
+ std::cerr << "Unknown error in eigenvalue analysis" << std::endl;
699
+ return false;
700
+ }
701
+ }
702
+
703
+ // Cubic equation analysis function
704
+ bool cubicAnalysis(double a, double y, double beta, int num_points, double z_min, double z_max, const std::string& output_file) {
705
+ std::cout << "Running cubic equation analysis with parameters: a = " << a
706
+ << ", y = " << y << ", beta = " << beta << ", num_points = " << num_points
707
+ << ", z_min = " << z_min << ", z_max = " << z_max << std::endl;
708
+ std::cout << "Output will be saved to: " << output_file << std::endl;
709
+
710
+ try {
711
+ // Compute Im(s) vs z data with z_min and z_max parameters
712
+ std::vector<std::vector<double>> ims_data = computeImSVsZ(a, y, beta, num_points, z_min, z_max);
713
+
714
+ // Save to JSON
715
+ if (!saveImSDataAsJSON(output_file, ims_data)) {
716
+ return false;
717
+ }
718
+
719
+ std::cout << "Cubic equation data saved to " << output_file << std::endl;
720
+ return true;
721
+ }
722
+ catch (const std::exception& e) {
723
+ std::cerr << "Error in cubic analysis: " << e.what() << std::endl;
724
+ return false;
725
+ }
726
+ catch (...) {
727
+ std::cerr << "Unknown error in cubic analysis" << std::endl;
728
+ return false;
729
+ }
730
+ }
731
+
732
+ int main(int argc, char* argv[]) {
733
+ // Print received arguments for debugging
734
+ std::cout << "Received " << argc << " arguments:" << std::endl;
735
+ for (int i = 0; i < argc; ++i) {
736
+ std::cout << " argv[" << i << "]: " << argv[i] << std::endl;
737
+ }
738
+
739
+ // Check for mode argument
740
+ if (argc < 2) {
741
+ std::cerr << "Error: Missing mode argument." << std::endl;
742
+ std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
743
+ std::cerr << " or: " << argv[0] << " cubic <a> <y> <beta> <num_points> <z_min> <z_max> <output_file>" << std::endl;
744
+ return 1;
745
+ }
746
+
747
+ std::string mode = argv[1];
748
+
749
+ try {
750
+ if (mode == "eigenvalues") {
751
+ // ─── Eigenvalue analysis mode ───────────────────────────────────────────
752
+ if (argc != 10) {
753
+ std::cerr << "Error: Incorrect number of arguments for eigenvalues mode." << std::endl;
754
+ std::cerr << "Usage: " << argv[0] << " eigenvalues <n> <p> <a> <y> <fineness> <theory_grid_points> <theory_tolerance> <output_file>" << std::endl;
755
+ std::cerr << "Received " << argc << " arguments, expected 10." << std::endl;
756
+ return 1;
757
+ }
758
+
759
+ int n = std::stoi(argv[2]);
760
+ int p = std::stoi(argv[3]);
761
+ double a = std::stod(argv[4]);
762
+ double y = std::stod(argv[5]);
763
+ int fineness = std::stoi(argv[6]);
764
+ int theory_grid_points = std::stoi(argv[7]);
765
+ double theory_tolerance = std::stod(argv[8]);
766
+ std::string output_file = argv[9];
767
+
768
+ if (!eigenvalueAnalysis(n, p, a, y, fineness, theory_grid_points, theory_tolerance, output_file)) {
769
+ return 1;
770
+ }
771
+
772
+ } else if (mode == "cubic") {
773
+ // ─── Cubic equation analysis mode ───────────────────────────────────────────
774
+ if (argc != 9) {
775
+ std::cerr << "Error: Incorrect number of arguments for cubic mode." << std::endl;
776
+ std::cerr << "Usage: " << argv[0] << " cubic <a> <y> <beta> <num_points> <z_min> <z_max> <output_file>" << std::endl;
777
+ std::cerr << "Received " << argc << " arguments, expected 9." << std::endl;
778
+ return 1;
779
+ }
780
+
781
+ double a = std::stod(argv[2]);
782
+ double y = std::stod(argv[3]);
783
+ double beta = std::stod(argv[4]);
784
+ int num_points = std::stoi(argv[5]);
785
+ double z_min = std::stod(argv[6]);
786
+ double z_max = std::stod(argv[7]);
787
+ std::string output_file = argv[8];
788
+
789
+ if (!cubicAnalysis(a, y, beta, num_points, z_min, z_max, output_file)) {
790
+ return 1;
791
+ }
792
+
793
+ } else {
794
+ std::cerr << "Error: Unknown mode: " << mode << std::endl;
795
+ std::cerr << "Use 'eigenvalues' or 'cubic'" << std::endl;
796
+ return 1;
797
+ }
798
+ }
799
+ catch (const std::exception& e) {
800
+ std::cerr << "Error: " << e.what() << std::endl;
801
+ return 1;
802
+ }
803
+
804
+ return 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  }
app.py CHANGED
The diff for this file is too large to render. See raw diff