LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
symbolic_quadric_classification.cpp
浏览该文件的文档.
1#include <stdexcept>
4
7
8#include <algorithm>
9#include <cmath>
10#include <limits>
11#include <utility>
12
13namespace LMCAS::detail {
14namespace {
15
17 double values[3];
18 double vectors[3][3];
22};
23
24double largest_off_diagonal(const double matrix[3][3], size_t& p, size_t& q)
25{
26 p = 0;
27 q = 1;
28 double off_diagonal = std::abs(matrix[p][q]);
29 for (size_t row = 0; row < 3; ++row) {
30 for (size_t column = row + 1; column < 3; ++column) {
31 const double candidate = std::abs(matrix[row][column]);
32 if (candidate > off_diagonal) {
33 off_diagonal = candidate;
34 p = row;
35 q = column;
36 }
37 }
38 }
39 return off_diagonal;
40}
41
43 double matrix[3][3], double vectors[3][3], size_t p, size_t q)
44{
45 const double angle = 0.5 * std::atan2(
46 2.0 * matrix[p][q], matrix[q][q] - matrix[p][p]);
47 const double cosine = std::cos(angle);
48 const double sine = std::sin(angle);
49 const double pp = matrix[p][p];
50 const double pq = matrix[p][q];
51 const double qq = matrix[q][q];
52
53 for (size_t axis = 0; axis < 3; ++axis) {
54 if (axis == p || axis == q) continue;
55 const double axis_p = matrix[axis][p];
56 const double axis_q = matrix[axis][q];
57 matrix[axis][p] = cosine * axis_p - sine * axis_q;
58 matrix[p][axis] = matrix[axis][p];
59 matrix[axis][q] = sine * axis_p + cosine * axis_q;
60 matrix[q][axis] = matrix[axis][q];
61 }
62 matrix[p][p] = cosine * cosine * pp -
63 2.0 * sine * cosine * pq +
64 sine * sine * qq;
65 matrix[q][q] = sine * sine * pp +
66 2.0 * sine * cosine * pq +
67 cosine * cosine * qq;
68 matrix[p][q] = 0.0;
69 matrix[q][p] = 0.0;
70
71 for (size_t axis = 0; axis < 3; ++axis) {
72 const double vector_p = vectors[axis][p];
73 const double vector_q = vectors[axis][q];
74 vectors[axis][p] = cosine * vector_p - sine * vector_q;
75 vectors[axis][q] = sine * vector_p + cosine * vector_q;
76 }
77}
78
80{
81 double matrix[3][3];
82 SymmetricEigen3 result{{0.0, 0.0, 0.0},
83 {{1.0, 0.0, 0.0},
84 {0.0, 1.0, 0.0},
85 {0.0, 0.0, 1.0}},
86 0.0,
87 0.0,
88 false};
89 for (size_t row = 0; row < 3; ++row) {
90 for (size_t column = 0; column < 3; ++column) {
91 result.coefficient_scale = std::max(
92 result.coefficient_scale, std::abs(input[row][column]));
93 }
94 }
95 if (result.coefficient_scale == 0.0) return result;
96 for (size_t row = 0; row < 3; ++row) {
97 for (size_t column = 0; column < 3; ++column) {
98 matrix[row][column] =
99 input[row][column] / result.coefficient_scale;
100 }
101 }
102 result.zero_tolerance =
103 256.0 * std::numeric_limits<double>::epsilon();
104
105 for (size_t iteration = 0; iteration < 64; ++iteration) {
106 size_t p;
107 size_t q;
108 if (largest_off_diagonal(matrix, p, q) <= result.zero_tolerance) break;
109 rotate_principal_axes(matrix, result.vectors, p, q);
110 }
111 for (size_t axis = 0; axis < 3; ++axis) {
112 result.values[axis] = matrix[axis][axis];
113 if (std::abs(result.values[axis]) <= result.zero_tolerance) {
114 result.unresolved_rank =
115 result.unresolved_rank || result.values[axis] != 0.0;
116 result.values[axis] = 0.0;
117 }
118 }
119 return result;
120}
121
123 const std::shared_ptr<SymbolicExpr>& expression,
125 const std::string& operation,
126 const std::string& message)
127try {
128 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
129 (context).limits().max_ast_nodes, "transform");
130 auto simplified = expression ? detail::make_expression_ptr(detail::normalize_node(detail::node(expression), transform_budget)) : nullptr;
131 if (!simplified || !node(simplified)) {
134 }
135 auto evaluated = evaluate_numeric(
136 *simplified, NumericBindings{}, context);
137 if (!evaluated) {
138 if (evaluated.error().code == CasErrc::Cancelled ||
139 evaluated.error().code == CasErrc::ResourceLimit) {
140 return Result<double>::failure(evaluated.error());
141 }
144 }
145 if (!evaluated.value().is_finite() ||
146 !std::isfinite(evaluated.value().value)) {
149 }
150 return Result<double>::success(evaluated.value().value);
151} catch (const CasError& error) {
152 return Result<double>::failure(error);
153} catch (const std::bad_alloc&) {
154 return Result<double>::failure(CasErrc::ResourceLimit, "allocation failed", operation);
155} catch (const std::length_error& error) {
157} catch (const std::exception& error) {
159}
160
162 const std::shared_ptr<SymbolicExpr>& expanded,
163 const std::string& variable,
165 const std::string& operation)
166try {
167 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
168 (context).limits().max_ast_nodes, "transform");
169 auto derivative = detail::make_expression_ptr(detail::differentiate_node(detail::node(expanded), variable, transform_budget));
170 if (!derivative || !node(derivative)) {
173 "quadric second derivative is outside the supported domain",
174 operation);
175 }
176 derivative = detail::make_expression_ptr(detail::differentiate_node(detail::node(derivative), variable, transform_budget));
178 derivative, context, operation,
179 "quadric second derivative coefficient cannot be evaluated");
180 if (!value) return value;
181 return Result<double>::success(value.value() / 2.0);
182} catch (const CasError& error) {
183 return Result<double>::failure(error);
184} catch (const std::bad_alloc&) {
185 return Result<double>::failure(CasErrc::ResourceLimit, "allocation failed", operation);
186} catch (const std::length_error& error) {
188} catch (const std::exception& error) {
190}
191
193 const std::shared_ptr<SymbolicExpr>& expanded,
194 const std::string& variable,
195 const std::vector<std::string>& vars,
197 const std::string& operation)
198try {
199 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
200 (context).limits().max_ast_nodes, "transform");
201 auto derivative = detail::make_expression_ptr(detail::differentiate_node(detail::node(expanded), variable, transform_budget));
202 if (!derivative || !node(derivative)) {
205 "quadric first derivative is outside the supported domain",
206 operation);
207 }
208 for (const auto& coordinate : vars) {
209 derivative = detail::make_expression_ptr(detail::substitute_node(detail::node(derivative), coordinate, detail::node(SymbolicExpr::number(0)), transform_budget));
210 if (!derivative || !node(derivative)) {
213 "quadric linear coefficient cannot be substituted",
214 operation);
215 }
216 }
218 derivative, context, operation,
219 "quadric linear coefficient cannot be evaluated");
220} catch (const CasError& error) {
221 return Result<double>::failure(error);
222} catch (const std::bad_alloc&) {
223 return Result<double>::failure(CasErrc::ResourceLimit, "allocation failed", operation);
224} catch (const std::length_error& error) {
226} catch (const std::exception& error) {
228}
229
231 const std::shared_ptr<SymbolicExpr>& expanded,
232 const std::vector<std::string>& vars,
234 const std::string& operation,
235 double quadratic_matrix[3][3],
236 double linear[3],
237 CasError& error)
238{
239 Result<double> diagonal[3] = {
243 Result<double> linear_coefficients[3] = {
247 for (size_t axis = 0; axis < 3; ++axis) {
248 if (!diagonal[axis]) {
249 error = std::move(diagonal[axis].error());
250 return false;
251 }
252 if (!linear_coefficients[axis]) {
253 error = std::move(linear_coefficients[axis].error());
254 return false;
255 }
256 }
257 for (size_t axis = 0; axis < 3; ++axis) {
258 quadratic_matrix[axis][axis] = diagonal[axis].value();
259 linear[axis] = linear_coefficients[axis].value();
260 }
261 return true;
262}
263
265 const std::shared_ptr<SymbolicExpr>& expanded,
266 const std::vector<std::string>& vars,
268 const std::string& operation,
269 double quadratic_matrix[3][3],
270 CasError& error)
271{
272 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
273 (context).limits().max_ast_nodes, "transform");
274 for (size_t i = 0; i < 3; ++i) {
275 for (size_t j = i + 1; j < 3; ++j) {
276 auto mixed = detail::make_expression_ptr(detail::differentiate_node(detail::node(expanded), vars[i], transform_budget));
277 if (!mixed || !node(mixed)) {
278 error = {
280 "quadric mixed derivative is outside the supported domain",
281 operation};
282 return false;
283 }
284 mixed = detail::make_expression_ptr(detail::differentiate_node(detail::node(mixed), vars[j], transform_budget));
285 auto mixed_value = finite_numeric_coeff(
286 mixed, context, operation,
287 "quadric mixed coefficient cannot be evaluated");
288 if (!mixed_value) {
289 error = std::move(mixed_value.error());
290 return false;
291 }
292 quadratic_matrix[i][j] = mixed_value.value() * 0.5;
293 quadratic_matrix[j][i] = quadratic_matrix[i][j];
294 }
295 }
296 return true;
297}
298
300 const SymmetricEigen3& principal,
301 const double coefficients[3],
302 double linear[3],
303 double linear_tolerance[3],
304 const std::string& operation,
305 CasError& error)
306{
307 double projection_scale[3] = {0.0, 0.0, 0.0};
308 for (size_t row = 0; row < 3; ++row) {
309 const double normalized =
310 coefficients[row] / principal.coefficient_scale;
311 if (!std::isfinite(normalized)) {
312 error = {
314 "quadric linear part is outside the supported scale",
315 operation};
316 return false;
317 }
318 for (size_t axis = 0; axis < 3; ++axis) {
319 const double term = principal.vectors[row][axis] * normalized;
320 projection_scale[axis] =
321 std::max(projection_scale[axis], std::abs(term));
322 linear[axis] = std::fma(
323 principal.vectors[row][axis], normalized, linear[axis]);
324 }
325 }
326 for (size_t axis = 0; axis < 3; ++axis) {
327 linear_tolerance[axis] =
328 256.0 * std::numeric_limits<double>::epsilon() *
329 projection_scale[axis];
330 }
331 return true;
332}
333
335 const SymmetricEigen3& principal,
336 const double linear[3],
337 const double linear_tolerance[3])
338{
339 for (size_t axis = 0; axis < 3; ++axis) {
340 if (principal.values[axis] == 0.0 &&
341 std::abs(linear[axis]) > linear_tolerance[axis]) {
342 return true;
343 }
344 }
345 return false;
346}
347
349 const std::shared_ptr<SymbolicExpr>& expanded,
350 const std::vector<std::string>& vars,
352 const std::string& operation)
353try {
354 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
355 (context).limits().max_ast_nodes, "transform");
356 auto constant = expanded;
357 for (const auto& coordinate : vars) {
358 constant = detail::make_expression_ptr(detail::substitute_node(detail::node(constant), coordinate, detail::node(SymbolicExpr::number(0)), transform_budget));
359 if (!constant || !node(constant)) {
362 "quadric constant term cannot be substituted",
363 operation);
364 }
365 }
367 constant, context, operation,
368 "quadric constant term cannot be evaluated");
369} catch (const CasError& error) {
370 return Result<double>::failure(error);
371} catch (const std::bad_alloc&) {
372 return Result<double>::failure(CasErrc::ResourceLimit, "allocation failed", operation);
373} catch (const std::length_error& error) {
375} catch (const std::exception& error) {
377}
378
380 double constant;
382};
383
385 const SymmetricEigen3& principal,
386 const double linear[3],
387 double constant,
388 const std::string& operation)
389{
390 double centered_constant = constant / principal.coefficient_scale;
391 if (!std::isfinite(centered_constant)) {
394 "quadric constant is outside the supported scale",
395 operation);
396 }
397 double centered_scale = std::max(1.0, std::abs(centered_constant));
398 for (size_t axis = 0; axis < 3; ++axis) {
399 if (principal.values[axis] == 0.0) continue;
400 const double factor =
401 (linear[axis] / principal.values[axis]) * 0.25;
402 const double shift = factor * linear[axis];
403 if (!std::isfinite(factor) || !std::isfinite(shift)) {
406 "quadric translation is outside the supported domain",
407 operation);
408 }
409 centered_constant = std::fma(
410 -factor, linear[axis], centered_constant);
411 centered_scale = std::max(centered_scale, std::abs(shift));
412 }
413 const double zero_tolerance =
414 256.0 * std::numeric_limits<double>::epsilon() * centered_scale;
415 return Result<CenteredQuadric>::success({centered_constant, zero_tolerance});
416}
417
419 int positive,
420 int negative,
421 double centered_constant,
422 double zero_tolerance,
423 const std::string& operation)
424{
425 if (std::abs(centered_constant) <= zero_tolerance) {
428 "rank-two quadric has a degenerate real locus",
429 operation);
430 }
431 const bool indefinite = positive == 1 && negative == 1;
432 const bool real_definite =
433 (positive == 2 && centered_constant < -zero_tolerance) ||
434 (negative == 2 && centered_constant > zero_tolerance);
435 if (indefinite || real_definite) {
436 return VectorStringResult::success("cylinder");
437 }
440 "rank-two quadric has no real cylinder locus",
441 operation);
442}
443
445 const SymmetricEigen3& principal,
446 int positive,
447 int negative,
448 double centered_constant,
449 double zero_tolerance,
450 const std::string& operation)
451{
452 const bool real_ellipsoid =
453 (positive == 3 && centered_constant < -zero_tolerance) ||
454 (negative == 3 && centered_constant > zero_tolerance);
455 if (!real_ellipsoid) {
458 std::abs(centered_constant) <= zero_tolerance
459 ? "definite quadric degenerates to a point"
460 : "definite quadric has no real locus",
461 operation);
462 }
463 const double min_value = std::min(
464 principal.values[0],
465 std::min(principal.values[1], principal.values[2]));
466 const double max_value = std::max(
467 principal.values[0],
468 std::max(principal.values[1], principal.values[2]));
469 if (max_value - min_value <= principal.zero_tolerance) {
470 return VectorStringResult::success("sphere");
471 }
472 return VectorStringResult::success("ellipsoid");
473}
474
476 const SymmetricEigen3& principal,
477 int rank,
478 double centered_constant,
479 double zero_tolerance,
480 const std::string& operation)
481{
482 const int positive =
483 (principal.values[0] > 0.0) +
484 (principal.values[1] > 0.0) +
485 (principal.values[2] > 0.0);
486 const int negative =
487 (principal.values[0] < 0.0) +
488 (principal.values[1] < 0.0) +
489 (principal.values[2] < 0.0);
490 if (rank == 2) {
492 positive, negative, centered_constant, zero_tolerance, operation);
493 }
494 if (rank == 3) {
495 if (negative == 0 || positive == 0) {
497 principal, positive, negative, centered_constant,
498 zero_tolerance, operation);
499 }
500 if (std::abs(centered_constant) <= zero_tolerance) {
501 return VectorStringResult::success("cone");
502 }
503 return VectorStringResult::success("hyperboloid");
504 }
507 "quadric is outside the currently classified support domain",
508 operation);
509}
510
512 const SymmetricEigen3& principal,
513 int rank,
514 const double linear[3],
515 const std::shared_ptr<SymbolicExpr>& expanded,
516 const std::vector<std::string>& vars,
518 const std::string& operation)
519{
520 auto constant = coefficient_of_constant(expanded, vars, context, operation);
521 if (!constant) return VectorStringResult::failure(std::move(constant.error()));
522 auto centered = center_quadric(
523 principal, linear, constant.value(), operation);
524 if (!centered) return VectorStringResult::failure(std::move(centered.error()));
525 const auto& value = centered.value();
527 principal, rank, value.constant, value.zero_tolerance, operation);
528}
529
530}
531
533 const SurfaceSymbolic& surf,
535 const std::string& operation)
536try {
537 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
538 (context).limits().max_ast_nodes, "transform");
539 const auto& vars = surf.vars;
540 auto expanded = detail::make_expression_ptr(detail::expand_node(detail::node(surf.F), transform_budget));
541 if (!expanded || !node(expanded)) expanded = surf.F;
542
543 double quadratic_matrix[3][3] = {};
544 double linear_coefficients[3];
545 CasError error;
546 if (!extract_axis_coefficients(
547 expanded, vars, context, operation, quadratic_matrix,
548 linear_coefficients, error)) {
549 return VectorStringResult::failure(std::move(error));
550 }
551 if (!extract_mixed_coefficients(
552 expanded, vars, context, operation, quadratic_matrix, error)) {
553 return VectorStringResult::failure(std::move(error));
554 }
555
556 const SymmetricEigen3 principal =
557 diagonalize_symmetric_3x3(quadratic_matrix);
558 if (principal.coefficient_scale == 0.0) {
561 "quadric has no nonzero quadratic part",
562 operation);
563 }
564 if (principal.unresolved_rank) {
567 "quadric rank is unresolved within the eigensolver error bound",
568 operation);
569 }
570
571 double linear[3] = {0.0, 0.0, 0.0};
572 double linear_tolerance[3];
573 if (!project_linear_coefficients(
574 principal, linear_coefficients, linear, linear_tolerance,
575 operation, error)) {
576 return VectorStringResult::failure(std::move(error));
577 }
578 const int rank =
579 (principal.values[0] != 0.0) +
580 (principal.values[1] != 0.0) +
581 (principal.values[2] != 0.0);
582
583 if (rank == 1) {
584 if (has_null_axis_linear_term(principal, linear, linear_tolerance)) {
585 return VectorStringResult::success("cylinder");
586 }
589 "rank-one quadric is degenerate or has no real locus",
590 operation);
591 }
592 if (rank == 2 &&
593 has_null_axis_linear_term(principal, linear, linear_tolerance)) {
594 return VectorStringResult::success("paraboloid");
595 }
596 return classify_translated_quadric(
597 principal, rank, linear, expanded, vars, context, operation);
598} catch (const CasError& error) {
599 return VectorStringResult::failure(error);
600} catch (const std::bad_alloc&) {
602} catch (const std::length_error& error) {
604} catch (const std::exception& error) {
606}
607
608}
static Result failure(CasErrc code, std::string message, std::string operation={})
static Result success(T value)
VectorStringResult classify_definite_locus(const SymmetricEigen3 &principal, int positive, int negative, double centered_constant, double zero_tolerance, const std::string &operation)
Result< double > coefficient_of_constant(const std::shared_ptr< SymbolicExpr > &expanded, const std::vector< std::string > &vars, ComputationContext &context, const std::string &operation)
Result< double > finite_numeric_coeff(const std::shared_ptr< SymbolicExpr > &expression, ComputationContext &context, const std::string &operation, const std::string &message)
bool project_linear_coefficients(const SymmetricEigen3 &principal, const double coefficients[3], double linear[3], double linear_tolerance[3], const std::string &operation, CasError &error)
VectorStringResult classify_rank_two_locus(int positive, int negative, double centered_constant, double zero_tolerance, const std::string &operation)
bool extract_axis_coefficients(const std::shared_ptr< SymbolicExpr > &expanded, const std::vector< std::string > &vars, ComputationContext &context, const std::string &operation, double quadratic_matrix[3][3], double linear[3], CasError &error)
VectorStringResult classify_translated_quadric(const SymmetricEigen3 &principal, int rank, const double linear[3], const std::shared_ptr< SymbolicExpr > &expanded, const std::vector< std::string > &vars, ComputationContext &context, const std::string &operation)
VectorStringResult classify_centered_locus(const SymmetricEigen3 &principal, int rank, double centered_constant, double zero_tolerance, const std::string &operation)
Result< double > coefficient_of_square(const std::shared_ptr< SymbolicExpr > &expanded, const std::string &variable, ComputationContext &context, const std::string &operation)
bool has_null_axis_linear_term(const SymmetricEigen3 &principal, const double linear[3], const double linear_tolerance[3])
bool extract_mixed_coefficients(const std::shared_ptr< SymbolicExpr > &expanded, const std::vector< std::string > &vars, ComputationContext &context, const std::string &operation, double quadratic_matrix[3][3], CasError &error)
Result< double > coefficient_of_linear(const std::shared_ptr< SymbolicExpr > &expanded, const std::string &variable, const std::vector< std::string > &vars, ComputationContext &context, const std::string &operation)
Result< CenteredQuadric > center_quadric(const SymmetricEigen3 &principal, const double linear[3], double constant, const std::string &operation)
void rotate_principal_axes(double matrix[3][3], double vectors[3][3], size_t p, size_t q)
多元 Hensel 提升(单变量步)
SymbolicNodePtr differentiate_node(const SymbolicNodePtr &node, const std::string &variable, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
SymbolicNodePtr expand_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
std::shared_ptr<::LMCAS::SymbolicExpr > make_expression_ptr(SymbolicNodePtr root)
const SymbolicNodePtr & node(const ::LMCAS::SymbolicExpr &expression) noexcept
VectorStringResult classify_quadric_impl(const SurfaceSymbolic &surf, ComputationContext &context, const std::string &operation)
const std::string & variable
ComputationContext const std::string & operation
SymbolicNodePtr normalize_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
std::size_t std::size_t ComputationContext & context
SymbolicNodePtr substitute_node(const SymbolicNodePtr &node, const std::string &variable, const SymbolicNodePtr &replacement, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
__attribute__((visibility("default"))) VectorCalculusFieldResult gradient_checked(const std __attribute__((visibility("default"))) VectorCalculusFieldResult gradient_checked(const std __attribute__((visibility("default"))) VectorField gradient(const std const std::vector< std::string > & vars
Result< std::string > VectorStringResult
const std::string const ExprPtr & value
Result< ApproxReal > evaluate_numeric(const SymbolicExpr &expression, const NumericBindings &bindings={})
std::shared_ptr< SymbolicExpr > F
曲面方程左端,等于零。
std::unordered_map< std::string, double > NumericBindings
std::vector< std::string > vars
坐标变量名 {x,y,z}。
内部 AST 定义。