euler314 commited on
Commit
fefe0f9
·
verified ·
1 Parent(s): dd04e12

Update cubic_cpp.cpp

Browse files
Files changed (1) hide show
  1. cubic_cpp.cpp +245 -266
cubic_cpp.cpp CHANGED
@@ -1,9 +1,9 @@
1
  #include <pybind11/pybind11.h>
2
  #include <pybind11/numpy.h>
3
  #include <pybind11/stl.h>
4
- #include <pybind11/complex.h>
 
5
  #include <vector>
6
- #include <complex>
7
  #include <cmath>
8
  #include <algorithm>
9
  #include <random>
@@ -30,6 +30,198 @@ double discriminant_func(double z, double beta, double z_a, double y) {
30
  std::pow(c/(3.0*a) - std::pow(b, 2)/(9.0*std::pow(a, 2)), 3);
31
  }
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  // Find zeros of discriminant
34
  std::vector<double> find_z_at_discriminant_zero(double z_a, double y, double beta,
35
  double z_min, double z_max, int steps) {
@@ -76,6 +268,7 @@ std::vector<double> find_z_at_discriminant_zero(double z_a, double y, double bet
76
  }
77
  if ((fm < 0 && f1_copy < 0) || (fm > 0 && f1_copy > 0)) {
78
  zl = mid;
 
79
  } else {
80
  zr = mid;
81
  }
@@ -117,90 +310,6 @@ sweep_beta_and_find_z_bounds(double z_a, double y, double z_min, double z_max,
117
  return std::make_tuple(betas, z_min_values, z_max_values);
118
  }
119
 
120
- // Compute cubic roots
121
- std::vector<std::complex<double>> compute_cubic_roots(double z, double beta, double z_a, double y) {
122
- double y_effective = apply_y_condition(y);
123
-
124
- // Coefficients
125
- double a = z * z_a;
126
- double b = z * z_a + z + z_a - z_a * y_effective;
127
- double c = z + z_a + 1.0 - y_effective * (beta * z_a + 1.0 - beta);
128
- double d = 1.0;
129
-
130
- std::vector<std::complex<double>> roots(3);
131
-
132
- // Handle special cases
133
- if (std::abs(a) < 1e-10) {
134
- if (std::abs(b) < 1e-10) { // Linear case
135
- roots[0] = std::complex<double>(-d/c, 0);
136
- roots[1] = std::complex<double>(0, 0);
137
- roots[2] = std::complex<double>(0, 0);
138
- } else { // Quadratic case
139
- double disc = c*c - 4.0*b*d;
140
- if (disc >= 0) {
141
- double sqrt_disc = std::sqrt(disc);
142
- roots[0] = std::complex<double>((-c + sqrt_disc) / (2.0 * b), 0);
143
- roots[1] = std::complex<double>((-c - sqrt_disc) / (2.0 * b), 0);
144
- } else {
145
- double sqrt_disc = std::sqrt(-disc);
146
- roots[0] = std::complex<double>(-c / (2.0 * b), sqrt_disc / (2.0 * b));
147
- roots[1] = std::complex<double>(-c / (2.0 * b), -sqrt_disc / (2.0 * b));
148
- }
149
- roots[2] = std::complex<double>(0, 0);
150
- }
151
- return roots;
152
- }
153
-
154
- // Normalize to form: x^3 + px^2 + qx + r = 0
155
- double p = b / a;
156
- double q = c / a;
157
- double r = d / a;
158
-
159
- // Depress the cubic: substitute x = y - p/3 to get y^3 + py + q = 0
160
- double p_over_3 = p / 3.0;
161
- double new_p = q - p * p / 3.0;
162
- double new_q = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
163
-
164
- // Calculate discriminant
165
- double discriminant = 4.0 * std::pow(new_p, 3) / 27.0 + new_q * new_q;
166
-
167
- if (std::abs(discriminant) < 1e-10) {
168
- // Three real roots, at least two are equal
169
- double u;
170
- if (std::abs(new_q) < 1e-10) {
171
- u = 0;
172
- } else {
173
- u = std::cbrt(-new_q / 2.0);
174
- }
175
- roots[0] = std::complex<double>(2.0 * u - p_over_3, 0);
176
- roots[1] = std::complex<double>(-u - p_over_3, 0);
177
- roots[2] = std::complex<double>(-u - p_over_3, 0);
178
- } else if (discriminant > 0) {
179
- // One real root, two complex conjugate roots
180
- double sqrt_disc = std::sqrt(discriminant);
181
- double u = std::cbrt(-new_q / 2.0 + sqrt_disc / 2.0);
182
- double v = std::cbrt(-new_q / 2.0 - sqrt_disc / 2.0);
183
-
184
- // Real root
185
- roots[0] = std::complex<double>(u + v - p_over_3, 0);
186
-
187
- // Complex roots
188
- const double sqrt3_over_2 = std::sqrt(3.0) / 2.0;
189
- roots[1] = std::complex<double>(-0.5 * (u + v) - p_over_3, sqrt3_over_2 * (u - v));
190
- roots[2] = std::complex<double>(-0.5 * (u + v) - p_over_3, -sqrt3_over_2 * (u - v));
191
- } else {
192
- // Three distinct real roots
193
- double theta = std::acos(-new_q / 2.0 / std::sqrt(-std::pow(new_p, 3) / 27.0));
194
- double sqrt_term = 2.0 * std::sqrt(-new_p / 3.0);
195
-
196
- roots[0] = std::complex<double>(sqrt_term * std::cos(theta / 3.0) - p_over_3, 0);
197
- roots[1] = std::complex<double>(sqrt_term * std::cos((theta + 2.0 * M_PI) / 3.0) - p_over_3, 0);
198
- roots[2] = std::complex<double>(sqrt_term * std::cos((theta + 4.0 * M_PI) / 3.0) - p_over_3, 0);
199
- }
200
-
201
- return roots;
202
- }
203
-
204
  // Compute high y curve
205
  std::vector<double> compute_high_y_curve(const std::vector<double>& betas, double z_a, double y) {
206
  double y_effective = apply_y_condition(y);
@@ -239,103 +348,25 @@ std::vector<double> compute_alternate_low_expr(const std::vector<double>& betas,
239
  return result;
240
  }
241
 
242
- // Compute max k expression
243
- std::vector<double> compute_max_k_expression(const std::vector<double>& betas, double z_a, double y, int k_samples=1000) {
244
- double y_effective = apply_y_condition(y);
245
  size_t n = betas.size();
246
  std::vector<double> result(n);
247
 
248
- // Sample k values on logarithmic scale
249
- std::vector<double> k_values(k_samples);
250
- double log_min = std::log(0.001);
251
- double log_max = std::log(1000.0);
252
- double log_step = (log_max - log_min) / (k_samples - 1);
253
-
254
- for (int i = 0; i < k_samples; i++) {
255
- k_values[i] = std::exp(log_min + i * log_step);
256
- }
257
-
258
  for (size_t i = 0; i < n; i++) {
259
- double beta = betas[i];
260
- std::vector<double> values(k_samples);
261
-
262
- for (int j = 0; j < k_samples; j++) {
263
- double k = k_values[j];
264
- double numerator = y_effective * beta * (z_a - 1.0) * k + (z_a * k + 1.0) * ((y_effective - 1.0) * k - 1.0);
265
- double denominator = (z_a * k + 1.0) * (k * k + k);
266
-
267
- if (std::abs(denominator) < 1e-10) {
268
- values[j] = std::numeric_limits<double>::quiet_NaN();
269
- } else {
270
- values[j] = numerator / denominator;
271
- }
272
- }
273
-
274
- // Find maximum value, ignoring NaNs
275
- double max_val = -std::numeric_limits<double>::infinity();
276
- bool found_valid = false;
277
-
278
- for (double val : values) {
279
- if (!std::isnan(val) && val > max_val) {
280
- max_val = val;
281
- found_valid = true;
282
- }
283
- }
284
-
285
- result[i] = found_valid ? max_val : std::numeric_limits<double>::quiet_NaN();
286
  }
287
 
288
  return result;
289
  }
290
 
291
- // Compute min t expression
292
- std::vector<double> compute_min_t_expression(const std::vector<double>& betas, double z_a, double y, int t_samples=1000) {
293
- double y_effective = apply_y_condition(y);
294
  size_t n = betas.size();
295
  std::vector<double> result(n);
296
 
297
- if (z_a <= 0) {
298
- std::fill(result.begin(), result.end(), std::numeric_limits<double>::quiet_NaN());
299
- return result;
300
- }
301
-
302
- // Sample t values in (-1/a, 0)
303
- double lower_bound = -1.0 / z_a + 1e-10; // Avoid division by zero
304
- std::vector<double> t_values(t_samples);
305
- double t_step = (0.0 - lower_bound) / (t_samples - 1);
306
-
307
- for (int i = 0; i < t_samples; i++) {
308
- t_values[i] = lower_bound + i * t_step * (1.0 - 1e-10); // Avoid exactly 0
309
- }
310
-
311
  for (size_t i = 0; i < n; i++) {
312
- double beta = betas[i];
313
- std::vector<double> values(t_samples);
314
-
315
- for (int j = 0; j < t_samples; j++) {
316
- double t = t_values[j];
317
- double numerator = y_effective * beta * (z_a - 1.0) * t + (z_a * t + 1.0) * ((y_effective - 1.0) * t - 1.0);
318
- double denominator = (z_a * t + 1.0) * (t * t + t);
319
-
320
- if (std::abs(denominator) < 1e-10) {
321
- values[j] = std::numeric_limits<double>::quiet_NaN();
322
- } else {
323
- values[j] = numerator / denominator;
324
- }
325
- }
326
-
327
- // Find minimum value, ignoring NaNs
328
- double min_val = std::numeric_limits<double>::infinity();
329
- bool found_valid = false;
330
-
331
- for (double val : values) {
332
- if (!std::isnan(val) && val < min_val) {
333
- min_val = val;
334
- found_valid = true;
335
- }
336
- }
337
-
338
- result[i] = found_valid ? min_val : std::numeric_limits<double>::quiet_NaN();
339
  }
340
 
341
  return result;
@@ -375,116 +406,60 @@ compute_derivatives(const std::vector<double>& curve, const std::vector<double>&
375
  return std::make_tuple(d1, d2);
376
  }
377
 
378
- // Generate eigenvalue distribution
379
  std::vector<double> generate_eigenvalue_distribution(double beta, double y, double z_a, int n, int seed) {
 
380
  double y_effective = apply_y_condition(y);
381
 
382
  // Set random seed
383
  std::mt19937 gen(seed);
384
- std::normal_distribution<double> normal_dist(0.0, 1.0);
385
 
386
  // Compute dimension p based on aspect ratio y
387
  int p = static_cast<int>(y_effective * n);
388
 
389
- // Create matrices - we'll use simple vectors and manual operations
390
- // since we're trying to avoid dependency on Eigen
391
-
392
- // Diagonal of T_n (Population/Shape Matrix)
393
- std::vector<double> diag_T(p);
394
- int k = static_cast<int>(std::floor(beta * p));
395
-
396
- // Fill diagonal entries
397
- for (int j = 0; j < k; j++) {
398
- diag_T[j] = z_a;
399
- }
400
- for (int j = k; j < p; j++) {
401
- diag_T[j] = 1.0;
402
- }
403
-
404
- // Shuffle diagonal entries
405
- std::shuffle(diag_T.begin(), diag_T.end(), gen);
406
-
407
- // Generate data matrix X
408
- std::vector<std::vector<double>> X(p, std::vector<double>(n));
409
  for (int i = 0; i < p; i++) {
410
  for (int j = 0; j < n; j++) {
411
- X[i][j] = normal_dist(gen);
412
  }
413
  }
414
 
415
- // Compute S_n = (1/n) * X*X^T
416
- std::vector<std::vector<double>> S(p, std::vector<double>(p, 0.0));
417
- for (int i = 0; i < p; i++) {
418
- for (int j = 0; j < p; j++) {
419
- double sum = 0.0;
420
- for (int k = 0; k < n; k++) {
421
- sum += X[i][k] * X[j][k];
422
- }
423
- S[i][j] = sum / n;
424
- }
425
- }
426
 
427
- // Compute B_n = S_n * diag(T_n)
428
- std::vector<std::vector<double>> B(p, std::vector<double>(p, 0.0));
429
  for (int i = 0; i < p; i++) {
430
- for (int j = 0; j < p; j++) {
431
- B[i][j] = S[i][j] * diag_T[j];
432
- }
433
  }
434
 
435
- // Find eigenvalues - use power iteration for largest/smallest eigenvalues
436
- // This is a simplified example and not recommended for production use
437
- // For real applications, use a proper eigenvalue solver
 
 
438
 
439
- // For simplicity, we'll just return some random values
440
- // In real application, you'd compute actual eigenvalues
441
  std::vector<double> eigenvalues(p);
442
  for (int i = 0; i < p; i++) {
443
- eigenvalues[i] = normal_dist(gen) + 1.0; // Dummy values
444
  }
445
 
446
  std::sort(eigenvalues.begin(), eigenvalues.end());
447
  return eigenvalues;
448
  }
449
 
450
- // Support boundaries
451
- std::tuple<std::vector<double>, std::vector<double>>
452
- compute_eigenvalue_support_boundaries(double z_a, double y, const std::vector<double>& beta_values,
453
- int n_samples, int seeds) {
454
- size_t num_betas = beta_values.size();
455
- std::vector<double> min_eigenvalues(num_betas);
456
- std::vector<double> max_eigenvalues(num_betas);
457
-
458
- for (size_t i = 0; i < num_betas; i++) {
459
- double beta = beta_values[i];
460
-
461
- std::vector<double> min_vals;
462
- std::vector<double> max_vals;
463
-
464
- // Run multiple trials
465
- for (int seed = 0; seed < seeds; seed++) {
466
- // Generate eigenvalues
467
- std::vector<double> eigenvalues = generate_eigenvalue_distribution(beta, y, z_a, n_samples, seed);
468
-
469
- // Get min and max
470
- if (!eigenvalues.empty()) {
471
- min_vals.push_back(eigenvalues.front());
472
- max_vals.push_back(eigenvalues.back());
473
- }
474
- }
475
-
476
- // Average over seeds
477
- double min_sum = 0.0, max_sum = 0.0;
478
- for (double val : min_vals) min_sum += val;
479
- for (double val : max_vals) max_sum += val;
480
-
481
- min_eigenvalues[i] = min_vals.empty() ? 0.0 : min_sum / min_vals.size();
482
- max_eigenvalues[i] = max_vals.empty() ? 0.0 : max_sum / max_vals.size();
483
- }
484
-
485
- return std::make_tuple(min_eigenvalues, max_eigenvalues);
486
- }
487
-
488
  // Python module definition
489
  PYBIND11_MODULE(cubic_cpp, m) {
490
  m.doc() = "C++ accelerated functions for cubic root analysis";
@@ -503,9 +478,18 @@ PYBIND11_MODULE(cubic_cpp, m) {
503
  py::arg("z_a"), py::arg("y"), py::arg("z_min"), py::arg("z_max"),
504
  py::arg("beta_steps"), py::arg("z_steps"));
505
 
506
- m.def("compute_cubic_roots", &compute_cubic_roots,
507
- "Compute roots of cubic equation",
508
- py::arg("z"), py::arg("beta"), py::arg("z_a"), py::arg("y"));
 
 
 
 
 
 
 
 
 
509
 
510
  m.def("compute_high_y_curve", &compute_high_y_curve,
511
  "Compute high y expression curve",
@@ -516,23 +500,18 @@ PYBIND11_MODULE(cubic_cpp, m) {
516
  py::arg("betas"), py::arg("z_a"), py::arg("y"));
517
 
518
  m.def("compute_max_k_expression", &compute_max_k_expression,
519
- "Compute max k expression",
520
- py::arg("betas"), py::arg("z_a"), py::arg("y"), py::arg("k_samples") = 1000);
521
 
522
  m.def("compute_min_t_expression", &compute_min_t_expression,
523
- "Compute min t expression",
524
- py::arg("betas"), py::arg("z_a"), py::arg("y"), py::arg("t_samples") = 1000);
525
 
526
  m.def("compute_derivatives", &compute_derivatives,
527
  "Compute first and second derivatives",
528
  py::arg("curve"), py::arg("betas"));
529
 
530
  m.def("generate_eigenvalue_distribution", &generate_eigenvalue_distribution,
531
- "Generate eigenvalue distribution",
532
  py::arg("beta"), py::arg("y"), py::arg("z_a"), py::arg("n"), py::arg("seed"));
533
-
534
- m.def("compute_eigenvalue_support_boundaries", &compute_eigenvalue_support_boundaries,
535
- "Compute eigenvalue support boundaries",
536
- py::arg("z_a"), py::arg("y"), py::arg("beta_values"),
537
- py::arg("n_samples"), py::arg("seeds"));
538
  }
 
1
  #include <pybind11/pybind11.h>
2
  #include <pybind11/numpy.h>
3
  #include <pybind11/stl.h>
4
+ #include <pybind11/eigen.h>
5
+ #include <Eigen/Dense>
6
  #include <vector>
 
7
  #include <cmath>
8
  #include <algorithm>
9
  #include <random>
 
30
  std::pow(c/(3.0*a) - std::pow(b, 2)/(9.0*std::pow(a, 2)), 3);
31
  }
32
 
33
+ // Function to compute the theoretical max value
34
+ double compute_theoretical_max(double a, double y, double beta) {
35
+ auto f = [a, y, beta](double k) -> double {
36
+ return (y * beta * (a - 1) * k + (a * k + 1) * ((y - 1) * k - 1)) /
37
+ ((a * k + 1) * (k * k + k) * y);
38
+ };
39
+
40
+ // Use numerical optimization to find the maximum
41
+ // Grid search followed by golden section search
42
+ double best_k = 1.0;
43
+ double best_val = f(best_k);
44
+
45
+ // Initial grid search over a wide range
46
+ const int num_grid_points = 200;
47
+ for (int i = 0; i < num_grid_points; ++i) {
48
+ double k = 0.01 + 100.0 * i / (num_grid_points - 1); // From 0.01 to 100
49
+ double val = f(k);
50
+ if (val > best_val) {
51
+ best_val = val;
52
+ best_k = k;
53
+ }
54
+ }
55
+
56
+ // Refine with golden section search
57
+ double a_gs = std::max(0.01, best_k / 10.0);
58
+ double b_gs = best_k * 10.0;
59
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
60
+ const double tolerance = 1e-10;
61
+
62
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
63
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
64
+
65
+ while (std::abs(b_gs - a_gs) > tolerance) {
66
+ if (f(c_gs) > f(d_gs)) {
67
+ b_gs = d_gs;
68
+ d_gs = c_gs;
69
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
70
+ } else {
71
+ a_gs = c_gs;
72
+ c_gs = d_gs;
73
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
74
+ }
75
+ }
76
+
77
+ return f((a_gs + b_gs) / 2.0);
78
+ }
79
+
80
+ // Function to compute the theoretical min value
81
+ double compute_theoretical_min(double a, double y, double beta) {
82
+ auto f = [a, y, beta](double t) -> double {
83
+ return (y * beta * (a - 1) * t + (a * t + 1) * ((y - 1) * t - 1)) /
84
+ ((a * t + 1) * (t * t + t) * y);
85
+ };
86
+
87
+ // Use numerical optimization to find the minimum
88
+ // Grid search followed by golden section search
89
+ double best_t = -0.5 / a; // Midpoint of (-1/a, 0)
90
+ double best_val = f(best_t);
91
+
92
+ // Initial grid search over the range (-1/a, 0)
93
+ const int num_grid_points = 200;
94
+ for (int i = 1; i < num_grid_points; ++i) {
95
+ // From slightly above -1/a to slightly below 0
96
+ double t = -0.999/a + 0.998/a * i / (num_grid_points - 1);
97
+ if (t >= 0 || t <= -1.0/a) continue; // Ensure t is in range (-1/a, 0)
98
+
99
+ double val = f(t);
100
+ if (val < best_val) {
101
+ best_val = val;
102
+ best_t = t;
103
+ }
104
+ }
105
+
106
+ // Refine with golden section search
107
+ double a_gs = -0.999/a; // Slightly above -1/a
108
+ double b_gs = -0.001/a; // Slightly below 0
109
+ const double golden_ratio = (1.0 + std::sqrt(5.0)) / 2.0;
110
+ const double tolerance = 1e-10;
111
+
112
+ double c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
113
+ double d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
114
+
115
+ while (std::abs(b_gs - a_gs) > tolerance) {
116
+ if (f(c_gs) < f(d_gs)) {
117
+ b_gs = d_gs;
118
+ d_gs = c_gs;
119
+ c_gs = b_gs - (b_gs - a_gs) / golden_ratio;
120
+ } else {
121
+ a_gs = c_gs;
122
+ c_gs = d_gs;
123
+ d_gs = a_gs + (b_gs - a_gs) / golden_ratio;
124
+ }
125
+ }
126
+
127
+ return f((a_gs + b_gs) / 2.0);
128
+ }
129
+
130
+ // Compute eigenvalues for a given beta value
131
+ std::tuple<double, double> compute_eigenvalues_for_beta(double z_a, double y, double beta, int n, int seed) {
132
+ // Apply the condition for y
133
+ double y_effective = apply_y_condition(y);
134
+
135
+ // Set random seed
136
+ std::mt19937 gen(seed);
137
+ std::normal_distribution<double> norm(0.0, 1.0);
138
+
139
+ // Compute dimension p based on aspect ratio y
140
+ int p = static_cast<int>(y_effective * n);
141
+
142
+ // Generate random matrix X
143
+ Eigen::MatrixXd X(p, n);
144
+ for (int i = 0; i < p; i++) {
145
+ for (int j = 0; j < n; j++) {
146
+ X(i, j) = norm(gen);
147
+ }
148
+ }
149
+
150
+ // Compute sample covariance matrix S_n = (1/n) * X * X^T
151
+ Eigen::MatrixXd S_n = (X * X.transpose()) / static_cast<double>(n);
152
+
153
+ // Build T_n diagonal matrix
154
+ int k = static_cast<int>(std::floor(beta * p));
155
+ std::vector<double> diags(p);
156
+ std::fill_n(diags.begin(), k, z_a);
157
+ std::fill_n(diags.begin() + k, p - k, 1.0);
158
+
159
+ // Shuffle diagonal entries
160
+ std::shuffle(diags.begin(), diags.end(), gen);
161
+
162
+ // Create T_n and its square root
163
+ Eigen::MatrixXd T_n = Eigen::MatrixXd::Zero(p, p);
164
+ Eigen::MatrixXd T_sqrt = Eigen::MatrixXd::Zero(p, p);
165
+
166
+ for (int i = 0; i < p; i++) {
167
+ double v = diags[i];
168
+ T_n(i, i) = v;
169
+ T_sqrt(i, i) = std::sqrt(v);
170
+ }
171
+
172
+ // Form B = T_sqrt * S_n * T_sqrt (symmetric)
173
+ Eigen::MatrixXd B = T_sqrt * S_n * T_sqrt;
174
+
175
+ // Compute eigenvalues of B
176
+ Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver(B);
177
+ Eigen::VectorXd eigenvalues = solver.eigenvalues();
178
+
179
+ // Return min and max eigenvalues
180
+ double min_eigenvalue = eigenvalues(0);
181
+ double max_eigenvalue = eigenvalues(p-1);
182
+
183
+ return std::make_tuple(min_eigenvalue, max_eigenvalue);
184
+ }
185
+
186
+ // Compute eigenvalue support boundaries
187
+ std::tuple<std::vector<double>, std::vector<double>, std::vector<double>, std::vector<double>>
188
+ compute_eigenvalue_support_boundaries(double z_a, double y, const std::vector<double>& beta_values,
189
+ int n_samples, int seeds) {
190
+ size_t num_betas = beta_values.size();
191
+ std::vector<double> min_eigenvalues(num_betas, 0.0);
192
+ std::vector<double> max_eigenvalues(num_betas, 0.0);
193
+ std::vector<double> theoretical_min_values(num_betas, 0.0);
194
+ std::vector<double> theoretical_max_values(num_betas, 0.0);
195
+
196
+ for (size_t i = 0; i < num_betas; i++) {
197
+ double beta = beta_values[i];
198
+
199
+ // Calculate theoretical values
200
+ theoretical_max_values[i] = compute_theoretical_max(z_a, y, beta);
201
+ theoretical_min_values[i] = compute_theoretical_min(z_a, y, beta);
202
+
203
+ std::vector<double> min_vals;
204
+ std::vector<double> max_vals;
205
+
206
+ // Run multiple trials with different seeds
207
+ for (int seed = 0; seed < seeds; seed++) {
208
+ auto [min_eig, max_eig] = compute_eigenvalues_for_beta(z_a, y, beta, n_samples, seed);
209
+ min_vals.push_back(min_eig);
210
+ max_vals.push_back(max_eig);
211
+ }
212
+
213
+ // Average over seeds
214
+ double min_sum = 0.0, max_sum = 0.0;
215
+ for (double val : min_vals) min_sum += val;
216
+ for (double val : max_vals) max_sum += val;
217
+
218
+ min_eigenvalues[i] = min_vals.empty() ? 0.0 : min_sum / min_vals.size();
219
+ max_eigenvalues[i] = max_vals.empty() ? 0.0 : max_sum / max_vals.size();
220
+ }
221
+
222
+ return std::make_tuple(min_eigenvalues, max_eigenvalues, theoretical_min_values, theoretical_max_values);
223
+ }
224
+
225
  // Find zeros of discriminant
226
  std::vector<double> find_z_at_discriminant_zero(double z_a, double y, double beta,
227
  double z_min, double z_max, int steps) {
 
268
  }
269
  if ((fm < 0 && f1_copy < 0) || (fm > 0 && f1_copy > 0)) {
270
  zl = mid;
271
+ f1_copy = fm;
272
  } else {
273
  zr = mid;
274
  }
 
310
  return std::make_tuple(betas, z_min_values, z_max_values);
311
  }
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  // Compute high y curve
314
  std::vector<double> compute_high_y_curve(const std::vector<double>& betas, double z_a, double y) {
315
  double y_effective = apply_y_condition(y);
 
348
  return result;
349
  }
350
 
351
+ // Compute max k expression over a range of betas
352
+ std::vector<double> compute_max_k_expression(const std::vector<double>& betas, double z_a, double y) {
 
353
  size_t n = betas.size();
354
  std::vector<double> result(n);
355
 
 
 
 
 
 
 
 
 
 
 
356
  for (size_t i = 0; i < n; i++) {
357
+ result[i] = compute_theoretical_max(z_a, y, betas[i]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  }
359
 
360
  return result;
361
  }
362
 
363
+ // Compute min t expression over a range of betas
364
+ std::vector<double> compute_min_t_expression(const std::vector<double>& betas, double z_a, double y) {
 
365
  size_t n = betas.size();
366
  std::vector<double> result(n);
367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  for (size_t i = 0; i < n; i++) {
369
+ result[i] = compute_theoretical_min(z_a, y, betas[i]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
371
 
372
  return result;
 
406
  return std::make_tuple(d1, d2);
407
  }
408
 
409
+ // Generate eigenvalue distribution for a specific beta
410
  std::vector<double> generate_eigenvalue_distribution(double beta, double y, double z_a, int n, int seed) {
411
+ // Apply the condition for y
412
  double y_effective = apply_y_condition(y);
413
 
414
  // Set random seed
415
  std::mt19937 gen(seed);
416
+ std::normal_distribution<double> norm(0.0, 1.0);
417
 
418
  // Compute dimension p based on aspect ratio y
419
  int p = static_cast<int>(y_effective * n);
420
 
421
+ // Generate random matrix X
422
+ Eigen::MatrixXd X(p, n);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  for (int i = 0; i < p; i++) {
424
  for (int j = 0; j < n; j++) {
425
+ X(i, j) = norm(gen);
426
  }
427
  }
428
 
429
+ // Compute sample covariance matrix S_n = (1/n) * X * X^T
430
+ Eigen::MatrixXd S_n = (X * X.transpose()) / static_cast<double>(n);
431
+
432
+ // Build T_n diagonal matrix
433
+ int k = static_cast<int>(std::floor(beta * p));
434
+ std::vector<double> diags(p);
435
+ std::fill_n(diags.begin(), k, z_a);
436
+ std::fill_n(diags.begin() + k, p - k, 1.0);
437
+
438
+ // Shuffle diagonal entries
439
+ std::shuffle(diags.begin(), diags.end(), gen);
440
 
441
+ // Create T_n
442
+ Eigen::MatrixXd T_n = Eigen::MatrixXd::Zero(p, p);
443
  for (int i = 0; i < p; i++) {
444
+ T_n(i, i) = diags[i];
 
 
445
  }
446
 
447
+ // Compute B_n = S_n * T_n
448
+ Eigen::MatrixXd B_n = S_n * T_n;
449
+
450
+ // Compute eigenvalues
451
+ Eigen::EigenSolver<Eigen::MatrixXd> solver(B_n);
452
 
453
+ // Extract and return real parts of eigenvalues
 
454
  std::vector<double> eigenvalues(p);
455
  for (int i = 0; i < p; i++) {
456
+ eigenvalues[i] = solver.eigenvalues()(i).real();
457
  }
458
 
459
  std::sort(eigenvalues.begin(), eigenvalues.end());
460
  return eigenvalues;
461
  }
462
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  // Python module definition
464
  PYBIND11_MODULE(cubic_cpp, m) {
465
  m.doc() = "C++ accelerated functions for cubic root analysis";
 
478
  py::arg("z_a"), py::arg("y"), py::arg("z_min"), py::arg("z_max"),
479
  py::arg("beta_steps"), py::arg("z_steps"));
480
 
481
+ m.def("compute_theoretical_max", &compute_theoretical_max,
482
+ "Compute theoretical maximum function value",
483
+ py::arg("a"), py::arg("y"), py::arg("beta"));
484
+
485
+ m.def("compute_theoretical_min", &compute_theoretical_min,
486
+ "Compute theoretical minimum function value",
487
+ py::arg("a"), py::arg("y"), py::arg("beta"));
488
+
489
+ m.def("compute_eigenvalue_support_boundaries", &compute_eigenvalue_support_boundaries,
490
+ "Compute empirical and theoretical eigenvalue support boundaries",
491
+ py::arg("z_a"), py::arg("y"), py::arg("beta_values"),
492
+ py::arg("n_samples"), py::arg("seeds"));
493
 
494
  m.def("compute_high_y_curve", &compute_high_y_curve,
495
  "Compute high y expression curve",
 
500
  py::arg("betas"), py::arg("z_a"), py::arg("y"));
501
 
502
  m.def("compute_max_k_expression", &compute_max_k_expression,
503
+ "Compute max k expression for multiple beta values",
504
+ py::arg("betas"), py::arg("z_a"), py::arg("y"));
505
 
506
  m.def("compute_min_t_expression", &compute_min_t_expression,
507
+ "Compute min t expression for multiple beta values",
508
+ py::arg("betas"), py::arg("z_a"), py::arg("y"));
509
 
510
  m.def("compute_derivatives", &compute_derivatives,
511
  "Compute first and second derivatives",
512
  py::arg("curve"), py::arg("betas"));
513
 
514
  m.def("generate_eigenvalue_distribution", &generate_eigenvalue_distribution,
515
+ "Generate eigenvalue distribution for a specific beta",
516
  py::arg("beta"), py::arg("y"), py::arg("z_a"), py::arg("n"), py::arg("seed"));
 
 
 
 
 
517
  }