euler314 commited on
Commit
b254716
·
verified ·
1 Parent(s): 677c19d

Update app.cpp

Browse files
Files changed (1) hide show
  1. app.cpp +185 -108
app.cpp CHANGED
@@ -1,4 +1,4 @@
1
- // app.cpp - Modified version for command line arguments with improved cubic solver
2
  #include <opencv2/opencv.hpp>
3
  #include <algorithm>
4
  #include <cmath>
@@ -21,8 +21,13 @@ struct CubicRoots {
21
  std::complex<double> root3;
22
  };
23
 
 
 
 
 
 
24
  // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
25
- // Improved to properly handle cases where roots should be one negative, one positive, one zero
26
  CubicRoots solveCubic(double a, double b, double c, double d) {
27
  // Constants for numerical stability
28
  const double epsilon = 1e-14;
@@ -61,6 +66,9 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
61
  return roots;
62
  }
63
 
 
 
 
64
  // Handle special case when d is zero - one root is zero
65
  if (std::abs(d) < epsilon) {
66
  // Factor out z: z(az^2 + bz + c) = 0
@@ -68,9 +76,9 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
68
  roots.root1 = std::complex<double>(0.0, 0.0); // One root is exactly zero
69
 
70
  // Solve the quadratic: az^2 + bz + c = 0
71
- double discriminant = b * b - 4.0 * a * c;
72
- if (discriminant >= 0) {
73
- double sqrtDiscriminant = std::sqrt(discriminant);
74
  roots.root2 = std::complex<double>((-b + sqrtDiscriminant) / (2.0 * a), 0.0);
75
  roots.root3 = std::complex<double>((-b - sqrtDiscriminant) / (2.0 * a), 0.0);
76
 
@@ -84,109 +92,96 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
84
  }
85
  } else {
86
  double real = -b / (2.0 * a);
87
- double imag = std::sqrt(-discriminant) / (2.0 * a);
88
  roots.root2 = std::complex<double>(real, imag);
89
  roots.root3 = std::complex<double>(real, -imag);
90
  }
91
  return roots;
92
  }
93
 
94
- // Normalize equation: z^3 + (b/a)z^2 + (c/a)z + (d/a) = 0
95
- double p = b / a;
96
- double q = c / a;
97
- double r = d / a;
98
-
99
- // Substitute z = t - p/3 to get t^3 + pt^2 + qt + r = 0
100
- double p1 = q - p * p / 3.0;
101
- double q1 = r - p * q / 3.0 + 2.0 * p * p * p / 27.0;
102
-
103
- // Calculate discriminant
104
- double D = q1 * q1 / 4.0 + p1 * p1 * p1 / 27.0;
105
-
106
- // Precompute values
107
- const double two_pi = 2.0 * M_PI;
108
- const double third = 1.0 / 3.0;
109
- const double p_over_3 = p / 3.0;
110
-
111
- CubicRoots roots;
112
-
113
- // Handle the special case where the discriminant is close to zero (all real roots, at least two equal)
114
- if (std::abs(D) < zero_threshold) {
115
- // Special case where all roots are zero
116
- if (std::abs(p1) < zero_threshold && std::abs(q1) < zero_threshold) {
117
- roots.root1 = std::complex<double>(-p_over_3, 0.0);
118
- roots.root2 = std::complex<double>(-p_over_3, 0.0);
119
- roots.root3 = std::complex<double>(-p_over_3, 0.0);
120
- return roots;
121
- }
122
-
123
- // General case for D ≈ 0
124
- double u = std::cbrt(-q1 / 2.0); // Real cube root
125
-
126
- roots.root1 = std::complex<double>(2.0 * u - p_over_3, 0.0);
127
- roots.root2 = std::complex<double>(-u - p_over_3, 0.0);
128
- roots.root3 = roots.root2; // Duplicate root
129
-
130
- // Check if any roots are close to zero and set them to exactly zero
131
- if (std::abs(roots.root1.real()) < zero_threshold)
132
- roots.root1 = std::complex<double>(0.0, 0.0);
133
- if (std::abs(roots.root2.real()) < zero_threshold) {
134
- roots.root2 = std::complex<double>(0.0, 0.0);
135
- roots.root3 = std::complex<double>(0.0, 0.0);
136
- }
137
 
138
- // Ensure pattern of one negative, one positive, one zero when possible
139
- if (roots.root1.real() != 0.0 && roots.root2.real() != 0.0) {
140
- if (roots.root1.real() > 0 && roots.root2.real() > 0) {
141
- roots.root2 = std::complex<double>(-std::abs(roots.root2.real()), 0.0);
142
- } else if (roots.root1.real() < 0 && roots.root2.real() < 0) {
143
- roots.root2 = std::complex<double>(std::abs(roots.root2.real()), 0.0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
  }
146
 
147
  return roots;
148
  }
149
 
150
- if (D > 0) { // One real root and two complex conjugate roots
151
- double sqrtD = std::sqrt(D);
152
- double u = std::cbrt(-q1 / 2.0 + sqrtD);
153
- double v = std::cbrt(-q1 / 2.0 - sqrtD);
 
154
 
155
- // Real root
156
- roots.root1 = std::complex<double>(u + v - p_over_3, 0.0);
 
157
 
158
- // Complex conjugate roots
159
- double real_part = -(u + v) / 2.0 - p_over_3;
160
- double imag_part = (u - v) * std::sqrt(3.0) / 2.0;
161
- roots.root2 = std::complex<double>(real_part, imag_part);
162
- roots.root3 = std::complex<double>(real_part, -imag_part);
163
 
164
- // Check if any roots are close to zero and set them to exactly zero
165
- if (std::abs(roots.root1.real()) < zero_threshold)
166
- roots.root1 = std::complex<double>(0.0, 0.0);
167
-
168
- return roots;
169
- }
170
- else { // Three distinct real roots
171
- double angle = std::acos(-q1 / 2.0 * std::sqrt(-27.0 / (p1 * p1 * p1)));
172
- double magnitude = 2.0 * std::sqrt(-p1 / 3.0);
173
-
174
- // Calculate all three real roots
175
- double root1_val = magnitude * std::cos(angle / 3.0) - p_over_3;
176
- double root2_val = magnitude * std::cos((angle + two_pi) / 3.0) - p_over_3;
177
- double root3_val = magnitude * std::cos((angle + 2.0 * two_pi) / 3.0) - p_over_3;
178
-
179
- // Sort roots to have one negative, one positive, one zero if possible
180
- std::vector<double> root_vals = {root1_val, root2_val, root3_val};
181
  std::sort(root_vals.begin(), root_vals.end());
182
 
183
- // Check for roots close to zero
184
- for (double& val : root_vals) {
185
- if (std::abs(val) < zero_threshold) {
186
- val = 0.0;
187
- }
188
- }
189
-
190
  // Count zeros, positives, and negatives
191
  int zeros = 0, positives = 0, negatives = 0;
192
  for (double val : root_vals) {
@@ -195,28 +190,110 @@ CubicRoots solveCubic(double a, double b, double c, double d) {
195
  else negatives++;
196
  }
197
 
198
- // If we have no zeros but have both positives and negatives, we're good
199
- // If we have zeros and both positives and negatives, we're good
200
- // If we only have one sign and zeros, we need to force one to be the opposite sign
201
- if (zeros == 0 && (positives == 0 || negatives == 0)) {
202
- // All same sign - force the middle value to be zero
203
- root_vals[1] = 0.0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  }
205
- else if (zeros > 0 && positives == 0 && negatives > 0) {
206
- // Only zeros and negatives - force one negative to be positive
207
- if (root_vals[2] == 0.0) root_vals[1] = std::abs(root_vals[0]);
208
- else root_vals[2] = std::abs(root_vals[0]);
 
 
 
 
 
 
 
209
  }
210
- else if (zeros > 0 && negatives == 0 && positives > 0) {
211
- // Only zeros and positives - force one positive to be negative
212
- if (root_vals[0] == 0.0) root_vals[1] = -std::abs(root_vals[2]);
213
- else root_vals[0] = -std::abs(root_vals[2]);
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
215
 
216
- // Assign roots
217
- roots.root1 = std::complex<double>(root_vals[0], 0.0);
218
- roots.root2 = std::complex<double>(root_vals[1], 0.0);
219
- roots.root3 = std::complex<double>(root_vals[2], 0.0);
220
 
221
  return roots;
222
  }
 
1
+ // app.cpp - Modified version with corrected cubic solver using discriminant
2
  #include <opencv2/opencv.hpp>
3
  #include <algorithm>
4
  #include <cmath>
 
21
  std::complex<double> root3;
22
  };
23
 
24
+ // Function to calculate discriminant of a cubic equation
25
+ double calculateCubicDiscriminant(double a, double b, double c, double d) {
26
+ return 18*a*b*c*d - 4*b*b*b*d + b*b*c*c - 4*a*c*c*c - 27*a*a*d*d;
27
+ }
28
+
29
  // Function to solve cubic equation: az^3 + bz^2 + cz + d = 0
30
+ // Improved to use discriminant for determining root patterns
31
  CubicRoots solveCubic(double a, double b, double c, double d) {
32
  // Constants for numerical stability
33
  const double epsilon = 1e-14;
 
66
  return roots;
67
  }
68
 
69
+ // Calculate the discriminant for the cubic equation
70
+ double discriminant = calculateCubicDiscriminant(a, b, c, d);
71
+
72
  // Handle special case when d is zero - one root is zero
73
  if (std::abs(d) < epsilon) {
74
  // Factor out z: z(az^2 + bz + c) = 0
 
76
  roots.root1 = std::complex<double>(0.0, 0.0); // One root is exactly zero
77
 
78
  // Solve the quadratic: az^2 + bz + c = 0
79
+ double quadDiscriminant = b * b - 4.0 * a * c;
80
+ if (quadDiscriminant >= 0) {
81
+ double sqrtDiscriminant = std::sqrt(quadDiscriminant);
82
  roots.root2 = std::complex<double>((-b + sqrtDiscriminant) / (2.0 * a), 0.0);
83
  roots.root3 = std::complex<double>((-b - sqrtDiscriminant) / (2.0 * a), 0.0);
84
 
 
92
  }
93
  } else {
94
  double real = -b / (2.0 * a);
95
+ double imag = std::sqrt(-quadDiscriminant) / (2.0 * a);
96
  roots.root2 = std::complex<double>(real, imag);
97
  roots.root3 = std::complex<double>(real, -imag);
98
  }
99
  return roots;
100
  }
101
 
102
+ // Convert to depressed cubic form t^3 + pt + q = 0
103
+ double p = (3.0*a*c - b*b) / (3.0*a*a);
104
+ double q = (2.0*b*b*b - 9.0*a*b*c + 27.0*a*a*d) / (27.0*a*a*a);
105
+
106
+ // Calculate discriminant in depressed form
107
+ double depressedDiscriminant = -(4.0*p*p*p + 27.0*q*q);
108
+
109
+ // Check if discriminant is very close to zero
110
+ if (std::abs(discriminant) < zero_threshold) {
111
+ // Multiple roots case (either triple root or double root)
112
+ double delta0 = b*b - 3.0*a*c;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ if (std::abs(delta0) < zero_threshold) {
115
+ // Triple root case
116
+ double root = -b / (3.0*a);
117
+ roots.root1 = std::complex<double>(root, 0.0);
118
+ roots.root2 = std::complex<double>(root, 0.0);
119
+ roots.root3 = std::complex<double>(root, 0.0);
120
+ } else {
121
+ // Double root + single root case
122
+ double doubleRoot = (9.0*a*d - b*c) / (2.0*delta0);
123
+ double singleRoot = (4.0*a*b*c - 9.0*a*a*d - b*b*b) / (a*delta0);
124
+
125
+ // Sort roots to ensure pattern: one root is negative, one is positive, one is zero or all are zero
126
+ if (std::abs(doubleRoot) < zero_threshold) doubleRoot = 0.0;
127
+ if (std::abs(singleRoot) < zero_threshold) singleRoot = 0.0;
128
+
129
+ // Assign roots to ensure the pattern
130
+ if (doubleRoot == 0.0) {
131
+ roots.root1 = std::complex<double>(0.0, 0.0);
132
+ roots.root2 = std::complex<double>(0.0, 0.0);
133
+ if (singleRoot > 0.0) {
134
+ roots.root3 = std::complex<double>(-singleRoot, 0.0); // Ensure we have a negative
135
+ } else {
136
+ roots.root3 = std::complex<double>(std::abs(singleRoot), 0.0); // Ensure we have a positive
137
+ }
138
+ } else if (singleRoot == 0.0) {
139
+ roots.root1 = std::complex<double>(0.0, 0.0);
140
+ if (doubleRoot > 0.0) {
141
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
142
+ roots.root3 = std::complex<double>(-doubleRoot, 0.0); // Ensure negative
143
+ } else {
144
+ roots.root2 = std::complex<double>(-doubleRoot, 0.0);
145
+ roots.root3 = std::complex<double>(std::abs(doubleRoot), 0.0); // Ensure positive
146
+ }
147
+ } else {
148
+ // Both non-zero, assign based on signs
149
+ if ((doubleRoot > 0 && singleRoot > 0) || (doubleRoot < 0 && singleRoot < 0)) {
150
+ // If same sign, make one zero
151
+ roots.root1 = std::complex<double>(0.0, 0.0);
152
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
153
+ roots.root3 = std::complex<double>(singleRoot, 0.0);
154
+ } else {
155
+ // Different signs, keep it
156
+ roots.root1 = std::complex<double>(0.0, 0.0);
157
+ roots.root2 = std::complex<double>(doubleRoot, 0.0);
158
+ roots.root3 = std::complex<double>(singleRoot, 0.0);
159
+ }
160
  }
161
  }
162
 
163
  return roots;
164
  }
165
 
166
+ // Standard case based on discriminant
167
+ if (discriminant > 0) {
168
+ // Three distinct real roots
169
+ double theta = std::acos(-q * std::sqrt(-27.0/(4.0*p*p*p)));
170
+ double coef = 2.0 * std::sqrt(-p/3.0);
171
 
172
+ double root1 = coef * std::cos(theta/3.0) - b/(3.0*a);
173
+ double root2 = coef * std::cos((theta + 2.0*M_PI)/3.0) - b/(3.0*a);
174
+ double root3 = coef * std::cos((theta + 4.0*M_PI)/3.0) - b/(3.0*a);
175
 
176
+ // Ensure zero roots are exactly zero
177
+ if (std::abs(root1) < zero_threshold) root1 = 0.0;
178
+ if (std::abs(root2) < zero_threshold) root2 = 0.0;
179
+ if (std::abs(root3) < zero_threshold) root3 = 0.0;
 
180
 
181
+ // Sort roots
182
+ std::vector<double> root_vals = {root1, root2, root3};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  std::sort(root_vals.begin(), root_vals.end());
184
 
 
 
 
 
 
 
 
185
  // Count zeros, positives, and negatives
186
  int zeros = 0, positives = 0, negatives = 0;
187
  for (double val : root_vals) {
 
190
  else negatives++;
191
  }
192
 
193
+ // Handle the root pattern
194
+ if (zeros == 0) {
195
+ // If no zeros, force middle root to zero
196
+ if (negatives > 0 && positives > 0) {
197
+ // Already has both signs, good
198
+ roots.root1 = std::complex<double>(root_vals[0], 0.0);
199
+ roots.root2 = std::complex<double>(0.0, 0.0);
200
+ roots.root3 = std::complex<double>(root_vals[2], 0.0);
201
+ } else {
202
+ // All same sign, force middle to zero and ensure one positive, one negative
203
+ roots.root1 = std::complex<double>(-std::abs(root_vals[0]), 0.0);
204
+ roots.root2 = std::complex<double>(0.0, 0.0);
205
+ roots.root3 = std::complex<double>(std::abs(root_vals[2]), 0.0);
206
+ }
207
+ } else if (zeros == 3) {
208
+ // All zeros
209
+ roots.root1 = std::complex<double>(0.0, 0.0);
210
+ roots.root2 = std::complex<double>(0.0, 0.0);
211
+ roots.root3 = std::complex<double>(0.0, 0.0);
212
+ } else {
213
+ // At least one zero, ensure we have a positive and negative
214
+ if (zeros == 1) {
215
+ if (negatives == 0) {
216
+ // No negatives, force one
217
+ if (root_vals[0] == 0.0) {
218
+ roots.root1 = std::complex<double>(0.0, 0.0);
219
+ roots.root2 = std::complex<double>(-std::abs(root_vals[1]), 0.0);
220
+ roots.root3 = std::complex<double>(root_vals[2], 0.0);
221
+ } else {
222
+ roots.root1 = std::complex<double>(-std::abs(root_vals[0]), 0.0);
223
+ roots.root2 = std::complex<double>(0.0, 0.0);
224
+ roots.root3 = std::complex<double>(root_vals[2], 0.0);
225
+ }
226
+ } else if (positives == 0) {
227
+ // No positives, force one
228
+ if (root_vals[2] == 0.0) {
229
+ roots.root1 = std::complex<double>(root_vals[0], 0.0);
230
+ roots.root2 = std::complex<double>(std::abs(root_vals[1]), 0.0);
231
+ roots.root3 = std::complex<double>(0.0, 0.0);
232
+ } else {
233
+ roots.root1 = std::complex<double>(root_vals[0], 0.0);
234
+ roots.root2 = std::complex<double>(0.0, 0.0);
235
+ roots.root3 = std::complex<double>(std::abs(root_vals[2]), 0.0);
236
+ }
237
+ } else {
238
+ // Has both positive and negative and one zero
239
+ roots.root1 = std::complex<double>(root_vals[0], 0.0);
240
+ roots.root2 = std::complex<double>(root_vals[1], 0.0);
241
+ roots.root3 = std::complex<double>(root_vals[2], 0.0);
242
+ }
243
+ } else {
244
+ // Two zeros
245
+ if (root_vals[0] != 0.0) {
246
+ // Negative and two zeros
247
+ roots.root1 = std::complex<double>(root_vals[0], 0.0);
248
+ roots.root2 = std::complex<double>(0.0, 0.0);
249
+ roots.root3 = std::complex<double>(0.0, 0.0);
250
+ } else if (root_vals[2] != 0.0) {
251
+ // Two zeros and positive
252
+ roots.root1 = std::complex<double>(0.0, 0.0);
253
+ roots.root2 = std::complex<double>(0.0, 0.0);
254
+ roots.root3 = std::complex<double>(root_vals[2], 0.0);
255
+ } else {
256
+ // Should not happen but handle it
257
+ roots.root1 = std::complex<double>(0.0, 0.0);
258
+ roots.root2 = std::complex<double>(0.0, 0.0);
259
+ roots.root3 = std::complex<double>(0.0, 0.0);
260
+ }
261
+ }
262
  }
263
+ return roots;
264
+ }
265
+ else {
266
+ // One real root and two complex conjugate roots
267
+
268
+ // Calculate the real root using Cardano's formula
269
+ double C;
270
+ if (q >= 0) {
271
+ C = std::cbrt(-q/2.0 + std::sqrt(-depressedDiscriminant/108.0));
272
+ } else {
273
+ C = -std::cbrt(q/2.0 + std::sqrt(-depressedDiscriminant/108.0));
274
  }
275
+
276
+ // The real root
277
+ double realRoot = 0.0;
278
+ if (std::abs(C) < epsilon) {
279
+ realRoot = 0.0;
280
+ } else {
281
+ realRoot = C - p/(3.0*C) - b/(3.0*a);
282
+ }
283
+
284
+ // The complex conjugate roots
285
+ double realPart = -realRoot/2.0 - b/(3.0*a);
286
+ double imagPart = std::sqrt(std::abs(discriminant))/(2.0*a);
287
+
288
+ // Check if real root is close to zero
289
+ if (std::abs(realRoot) < zero_threshold) {
290
+ realRoot = 0.0;
291
  }
292
 
293
+ // Assign the roots with one real and two complex conjugates
294
+ roots.root1 = std::complex<double>(realRoot, 0.0);
295
+ roots.root2 = std::complex<double>(realPart, imagPart);
296
+ roots.root3 = std::complex<double>(realPart, -imagPart);
297
 
298
  return roots;
299
  }