LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
residual_verification.cpp
浏览该文件的文档.
2
7#include "poly_utils.hpp"
8#include "irrational.hpp"
15
16#include <algorithm>
17#include <exception>
18#include <new>
19#include <limits>
20#include <optional>
21#include <utility>
22
23
24namespace LMCAS {
25namespace {
26
29
38
39/**
40 * @brief 在原公共定义域上证明形式有理恒等式。
41 * 将不透明标量子式视为不定元,保留其点值可能为零的情况;
42 * 交叉相乘免去因式分解及符号系数 GCD。
43 */
45 struct Fraction { MultiPoly numerator; MultiPoly denominator; };
49 std::size_t canonical_nodes_ = 0;
50 std::vector<Node> atoms_;
51 std::vector<std::string> variables_;
52 std::vector<std::optional<Rational>> radical_squares_;
53 std::vector<Node> radical_radicands_;
54 std::vector<std::pair<Node, Node>> canonical_atoms_;
55 std::optional<CasError> failure_;
56 struct Angle {
58 Rational scale;
60 };
61 std::vector<Angle> angles_;
62
63 bool checked(Result<void> result) {
64 if (result) return true;
65 failure_ = result.error();
66 return false;
67 }
68 std::optional<std::int64_t> integer_power(const PowerNode& power) {
69 BigInt exponent;
71 std::dynamic_pointer_cast<const NumberNode>(power.exponent()), exponent))
72 return std::nullopt;
73 return exponent.try_to_int64();
74 }
76 const std::vector<Node>& operands, bool canonicalize_radicals) {
77 for (const auto& child : operands) {
78 if (!collect(child, canonicalize_radicals)) {
79 return false;
80 }
81 }
82 return true;
83 }
84 std::optional<bool> collect_integer_power(
85 const PowerNode& power, bool canonicalize_radicals) {
86 const auto exponent = integer_power(power);
87 if (!exponent) {
88 return std::nullopt;
89 }
90 if (*exponent < -1000 || *exponent > 1000) {
91 return false;
92 }
93 return collect(power.base(), canonicalize_radicals);
94 }
95 bool collect(const Node& node, bool canonicalize_radicals = true) {
96 if (!checked(context_.consume_steps(1, "residual.formal"))) {
97 return false;
98 }
99 if (!checked(context_.enter_recursion("residual.formal"))) {
100 return false;
101 }
103 for (const auto& atom : atoms_) {
104 if (atom->equals(*node)) {
105 return true;
106 }
107 }
108 if (dynamic_cast<const NumberNode*>(node.get())) {
109 return true;
110 }
111 if (auto sum = dynamic_cast<const AddNode*>(node.get())) {
112 return collect_operands(
113 sum->operands(), canonicalize_radicals);
114 }
115 if (auto product = dynamic_cast<const MultiplyNode*>(node.get())) {
116 return collect_operands(
117 product->operands(), canonicalize_radicals);
118 }
119 if (auto power = dynamic_cast<const PowerNode*>(node.get())) {
120 auto collected =
121 collect_integer_power(*power, canonicalize_radicals);
122 if (collected) {
123 return *collected;
124 }
125 }
126 return register_atom(node, canonicalize_radicals);
127 }
129 if (auto function = dynamic_cast<const FunctionNode*>(node.get())) {
130 if (function->type() == FunctionNode::FuncType::Sqrt &&
131 function->arguments().size() == 1) {
132 return function->arguments()[0];
133 }
134 } else if (auto power = dynamic_cast<const PowerNode*>(node.get())) {
135 auto exponent = detail::exact_rational_value(power->exponent());
136 if (exponent && *exponent == Rational(1, 2)) {
137 return power->base();
138 }
139 }
140 return nullptr;
141 }
142 std::optional<Rational> radical_square(const Node& node) {
143 const auto radicand = radical_radicand(node);
144 return radicand ? detail::exact_rational_value(radicand) : std::nullopt;
145 }
149 }
150 Node power(const Node& base, int exponent) {
151 auto degree = number(exponent);
152 normalization_check_children(&budget_, 1, base, degree);
153 return detail::make_node<PowerNode>(base, std::move(degree));
154 }
155 Node function(FunctionNode::FuncType type, const Node& argument) {
157 return detail::make_node<FunctionNode>(type, std::vector<Node>{argument});
158 }
159 Node product(std::vector<Node> operands) {
160 return SymbolicFactory::create_multiply(std::move(operands), &budget_);
161 }
162 Node sum(std::vector<Node> operands) {
163 return SymbolicFactory::create_add(std::move(operands), &budget_);
164 }
165 Node square_relation(const Node& node) {
166 if (auto radicand = radical_radicand(node)) { return radicand; }
167 const auto* fn = dynamic_cast<const FunctionNode*>(node.get());
168 if (!fn || fn->arguments().size() != 1) { return nullptr; }
169 using FT = FunctionNode::FuncType;
170 const auto& argument = fn->arguments()[0];
171 if (fn->type() == FT::Cos) {
172 return sum({number(1), product({
173 number(-1), power(function(FT::Sin, argument), 2)})});
174 }
175 if (fn->type() == FT::Cosh) {
176 return sum({number(1), power(function(FT::Sinh, argument), 2)});
177 }
178 if (fn->type() == FT::Abs) {
179 std::optional<detail::AssumptionFacts> assumed;
180 if (context_.assumptions()) { assumed.emplace(*context_.assumptions()); }
181 const auto& facts = assumed
182 ? static_cast<const FactsQuery&>(*assumed) : detail::no_facts();
183 auto real = detail::query_real_value(argument, facts, context_);
184 if (!real) { failure_ = real.error(); return nullptr; }
185 if (real.value() == Tribool::True) { return power(argument, 2); }
186 }
187 return nullptr;
188 }
189 bool register_replacement(const Node& node, Node replacement) {
190 budget_.measure(replacement);
191 if (!collect(replacement)) { return false; }
193 atoms_.size() + canonical_atoms_.size() + 1, "residual.formal"))) {
194 return false;
195 }
197 canonical_atoms_.emplace_back(node, std::move(replacement));
198 return true;
199 }
200 std::pair<Rational, Node> coefficient(const Node& argument) {
201 auto value = Rational(1);
202 const auto* mul = dynamic_cast<const MultiplyNode*>(argument.get());
203 if (!mul) { return {value, argument}; }
204 std::vector<Node> factors;
205 for (const auto& child : mul->operands()) {
206 if (auto exact = detail::exact_rational_value(child)) { value = value * *exact; }
207 else { factors.push_back(child); }
208 }
209 return {value, product(std::move(factors))};
210 }
211 void remember_angle(const FunctionNode& fn) {
212 using FT = FunctionNode::FuncType;
213 const bool hyperbolic = fn.type() == FT::Sinh || fn.type() == FT::Cosh ||
214 fn.type() == FT::Tanh;
215 if (fn.arguments().size() != 1 ||
216 (!hyperbolic && fn.type() != FT::Sin && fn.type() != FT::Cos &&
217 fn.type() != FT::Tan && fn.type() != FT::Cot &&
218 fn.type() != FT::Sec && fn.type() != FT::Csc)) { return; }
219 auto argument = detail::normalize_node(fn.arguments()[0], budget_);
220 auto [scale, base] = coefficient(argument);
221 if (scale < Rational(0)) { scale = -scale; }
222 if (scale.is_zero()) { return; }
223 for (const auto& angle : angles_) {
225 if (angle.hyperbolic == hyperbolic && angle.scale == scale &&
226 angle.base->equals(*base)) { return; }
227 }
229 "residual.formal"))) { return; }
231 angles_.push_back({std::move(base), std::move(scale), hyperbolic});
232 }
235 public:
237 : RecursiveSymbolicVisitor(&owner.budget_), owner_(owner) {}
238 void visit(const FunctionNode& fn) override {
240 RecursiveSymbolicVisitor::visit(fn);
241 }
242 };
244 const Rational& scale, const Node& base, bool hyperbolic) {
245 std::int64_t multiple = 1;
246 for (const auto& angle : angles_) {
248 if (angle.hyperbolic != hyperbolic || !angle.base->equals(*base)) { continue; }
249 auto ratio = scale / angle.scale;
250 if (ratio.get_denominator() != BigInt(1)) { continue; }
251 auto integer = ratio.get_numerator().try_to_int64();
252 if (integer && *integer > multiple && *integer <= 1000 &&
253 (*integer & (*integer - 1)) == 0) { multiple = *integer; }
254 }
255 return multiple;
256 }
257 Node rewrite_radical(const Node& node) {
258 auto radicand = radical_radicand(node);
259 if (!radicand || detail::exact_rational_value(radicand)) { return nullptr; }
260 auto canonical = detail::expand_node(radicand, budget_);
261 Node term = canonical;
262 if (const auto* add = dynamic_cast<const AddNode*>(canonical.get())) {
263 for (const auto& child : add->operands()) {
264 if (!dynamic_cast<const NumberNode*>(child.get())) { term = child; break; }
265 }
266 }
267 auto scale = coefficient(term).first;
268 if (scale < Rational(0)) { scale = -scale; }
269 if (scale.is_zero()) { return nullptr; }
270 if (scale != Rational(1)) {
271 canonical = detail::expand_node(product({
272 SymbolicFactory::create_number(Rational(1) / scale), canonical}), budget_);
273 }
274 if (scale == Rational(1) && canonical->equals(*radicand)) { return nullptr; }
275 auto half = SymbolicFactory::create_number(Rational(1, 2));
276 normalization_check_children(&budget_, 1, canonical, half);
277 auto radical = detail::make_node<PowerNode>(canonical, half);
278 if (scale == Rational(1)) { return radical; }
279 auto scalar = SymbolicFactory::create_number(scale);
280 normalization_check_children(&budget_, 1, scalar, half);
281 return product({detail::make_node<PowerNode>(scalar, half), radical});
282 }
284 if (fn.arguments().size() != 1) { return nullptr; }
285 using FT = FunctionNode::FuncType;
286 const auto& argument = fn.arguments()[0];
287 if (fn.type() == FT::Sqrt) {
288 auto half = SymbolicFactory::create_number(Rational(1, 2));
289 normalization_check_children(&budget_, 1, argument, half);
290 return detail::make_node<PowerNode>(argument, std::move(half));
291 }
292 if (fn.type() == FT::Tan || fn.type() == FT::Cot ||
293 fn.type() == FT::Sec || fn.type() == FT::Csc) {
294 auto sine = function(FT::Sin, argument);
295 auto cosine = function(FT::Cos, argument);
296 if (fn.type() == FT::Tan) { return product({sine, power(cosine, -1)}); }
297 if (fn.type() == FT::Cot) { return product({cosine, power(sine, -1)}); }
298 return power(fn.type() == FT::Sec ? cosine : sine, -1);
299 }
300 if (fn.type() == FT::Tanh) {
301 return product({function(FT::Sinh, argument),
302 power(function(FT::Cosh, argument), -1)});
303 }
304 if (fn.type() != FT::Sin && fn.type() != FT::Cos &&
305 fn.type() != FT::Sinh && fn.type() != FT::Cosh &&
306 fn.type() != FT::Exp) { return nullptr; }
307 auto canonical = detail::normalize_node(argument, budget_);
308 if (!canonical->equals(*argument)) { return function(fn.type(), canonical); }
309 if (fn.type() == FT::Exp) {
310 if (auto exact = detail::exact_rational_value(argument);
311 exact && exact->is_zero()) { return number(1); }
312 if (const auto* add = dynamic_cast<const AddNode*>(argument.get())) {
313 std::vector<Node> factors;
314 for (const auto& child : add->operands()) {
315 factors.push_back(function(FT::Exp, child));
316 }
317 return product(std::move(factors));
318 }
319 }
320 auto [scale, base] = coefficient(argument);
321 auto integer = scale.get_denominator() == BigInt(1)
322 ? scale.get_numerator().try_to_int64() : std::optional<std::int64_t>{};
323 if (fn.type() == FT::Exp) {
324 if (integer && *integer != 1 && *integer >= -1000 && *integer <= 1000) {
325 return power(function(FT::Exp, base), static_cast<int>(*integer));
326 }
327 if (scale < Rational(0)) {
328 auto positive = product({SymbolicFactory::create_number(-scale), base});
329 return power(function(FT::Exp, positive), -1);
330 }
331 return nullptr;
332 }
333 const bool odd = fn.type() == FT::Sin || fn.type() == FT::Sinh;
334 if (scale < Rational(0)) {
335 auto positive = product({SymbolicFactory::create_number(-scale), base});
336 auto value = function(fn.type(), positive);
337 return odd ? product({number(-1), value}) : value;
338 }
339 const bool hyperbolic = fn.type() == FT::Sinh || fn.type() == FT::Cosh;
340 if (double_angle_multiple(scale, base, hyperbolic) == 1) { return nullptr; }
341 auto half = product({SymbolicFactory::create_number(scale / Rational(2)), base});
342 auto sine = function(hyperbolic ? FT::Sinh : FT::Sin, half);
343 auto cosine = function(hyperbolic ? FT::Cosh : FT::Cos, half);
344 if (odd) { return product({number(2), sine, cosine}); }
345 return hyperbolic
346 ? sum({power(cosine, 2), power(sine, 2)})
347 : sum({power(cosine, 2), product({number(-1), power(sine, 2)})});
348 }
349
350 bool register_atom(const Node& node, bool canonicalize_radicals) {
351 if (!dynamic_cast<const VariableNode*>(node.get()) &&
352 !dynamic_cast<const FunctionNode*>(node.get()) &&
353 !dynamic_cast<const PowerNode*>(node.get()) &&
354 !dynamic_cast<const RootOfNode*>(node.get())) { return false; }
355 for (const auto& atom : atoms_) { if (atom->equals(*node)) { return true; } }
356 for (const auto& atom : canonical_atoms_)
357 { if (atom.first->equals(*node)) { return true; } }
358 return register_new_atom(node, canonicalize_radicals);
359 }
360 std::optional<bool> register_canonical_radical(
361 const Node& node, const Rational& square) {
362 const auto& numerator = square.get_numerator();
363 const auto& denominator = square.get_denominator();
365 numerator.bit_length() + denominator.bit_length() + 2,
366 "residual.formal"))) {
367 return false;
368 }
369 auto root = Irrational::sqrt_checked(
370 numerator * denominator, 1.0, context_);
371 if (!root) {
372 failure_ = root.error();
373 return false;
374 }
375 auto canonical = detail::node(denominator == BigInt(1)
376 ? root.value().to_symbolic()
377 : (root.value() * Rational(BigInt(1), denominator)).to_symbolic());
378 if (canonical->equals(*node)) {
379 return std::nullopt;
380 }
381 if (!collect(canonical, false) ||
383 atoms_.size() + canonical_atoms_.size() + 1,
384 "residual.formal"))) {
385 return false;
386 }
388 canonical_atoms_.emplace_back(node, std::move(canonical));
389 return true;
390 }
391
393 const Node& node, std::optional<Rational> square) {
394 const auto radicand = square_relation(node);
395 if (radicand && !square && !collect(radicand)) {
396 return false;
397 }
398 if (failure_) { return false; }
399 if (radicand && !square) {
401 }
403 atoms_.size() + canonical_atoms_.size() + 1,
404 "residual.formal"))) {
405 return false;
406 }
407 atoms_.push_back(node);
408 variables_.push_back(
409 "_formal" + std::to_string(variables_.size()));
410 radical_squares_.push_back(std::move(square));
411 radical_radicands_.push_back(
412 radical_squares_.back() ? nullptr : radicand);
413 return true;
414 }
415
416 bool register_new_atom(const Node& node, bool canonicalize_radicals) {
417 if (const auto* fn = dynamic_cast<const FunctionNode*>(node.get())) {
418 if (auto replacement = rewrite_function(*fn)) {
419 return register_replacement(node, std::move(replacement));
420 }
421 }
422 if (const auto* exponentiation = dynamic_cast<const PowerNode*>(node.get())) {
423 auto exponent = detail::exact_rational_value(exponentiation->exponent());
424 if (exponent && exponent->get_denominator() == BigInt(2) &&
425 *exponent != Rational(1, 2)) {
426 auto degree = exponent->get_numerator().try_to_int64();
427 if (degree && *degree >= -1000 && *degree <= 1000) {
428 auto half = SymbolicFactory::create_number(Rational(1, 2));
429 normalization_check_children(&budget_, 1, exponentiation->base(), half);
430 auto radical = detail::make_node<PowerNode>(exponentiation->base(), half);
431 return register_replacement(node, power(radical, static_cast<int>(*degree)));
432 }
433 }
434 }
435 if (auto replacement = rewrite_radical(node)) {
436 return register_replacement(node, std::move(replacement));
437 }
438 auto square = radical_square(node);
439 if (canonicalize_radicals && square &&
440 !square->get_numerator().is_negative()) {
441 auto registered =
442 register_canonical_radical(node, *square);
443 if (registered) {
444 return *registered;
445 }
446 }
447 return register_regular_atom(node, std::move(square));
448 }
449 MultiPoly constant(const Rational& value) const { return MultiPoly(value, variables_); }
450 std::optional<MultiPoly> multiply(const MultiPoly& a, const MultiPoly& b) {
451 const auto count = static_cast<std::size_t>(a.num_terms()) *
452 static_cast<std::size_t>(b.num_terms());
453 if (!checked(context_.require_expansion_terms(count, "residual.formal")) ||
454 !checked(context_.consume_steps(count + 1, "residual.formal"))) return std::nullopt;
455 std::size_t a_bits = 0, b_bits = 0;
456 std::int64_t a_degree = 0, b_degree = 0;
457 for (const auto& term : a.terms()) {
458 a_bits = std::max(a_bits, term.second.get_numerator().bit_length() +
459 term.second.get_denominator().bit_length());
460 std::int64_t degree = 0;
461 for (int exponent : term.first) degree += exponent;
462 a_degree = std::max(a_degree, degree);
463 }
464 for (const auto& term : b.terms()) {
465 b_bits = std::max(b_bits, term.second.get_numerator().bit_length() +
466 term.second.get_denominator().bit_length());
467 std::int64_t degree = 0;
468 for (int exponent : term.first) degree += exponent;
469 b_degree = std::max(b_degree, degree);
470 }
471 if (a_degree + b_degree > std::numeric_limits<int>::max()) {
473 "formal polynomial degree exceeds its representation", "residual.formal"));
474 return std::nullopt;
475 }
477 (a_bits + b_bits + 1) * (count + 1), "residual.formal"))) return std::nullopt;
478 return a * b;
479 }
480 std::optional<Fraction> combine(Fraction a, Fraction b, bool sum) {
481 auto denominator = multiply(a.denominator, b.denominator);
482 if (!denominator) return std::nullopt;
483 if (!sum) {
484 auto numerator = multiply(a.numerator, b.numerator);
485 if (!numerator) return std::nullopt;
486 return Fraction{std::move(*numerator), std::move(*denominator)};
487 }
488 auto left = multiply(a.numerator, b.denominator);
489 auto right = multiply(b.numerator, a.denominator);
490 if (!left || !right) return std::nullopt;
492 left->num_terms() + right->num_terms(), "residual.formal"))) return std::nullopt;
493 std::size_t left_bits = 0, right_bits = 0;
494 for (const auto& term : left->terms())
495 left_bits = std::max(left_bits, term.second.get_numerator().bit_length() +
496 term.second.get_denominator().bit_length());
497 for (const auto& term : right->terms())
498 right_bits = std::max(right_bits, term.second.get_numerator().bit_length() +
499 term.second.get_denominator().bit_length());
501 left_bits + right_bits + 1, "residual.formal"))) return std::nullopt;
502 return Fraction{*left + *right, std::move(*denominator)};
503 }
504 std::optional<Fraction> convert_integer_power(const PowerNode& power, std::int64_t n) {
505 auto base = convert(power.base());
506 if (!base) { return std::nullopt; }
507 if (n < 0) { std::swap(base->numerator, base->denominator); n = -n; }
508 std::optional<Fraction> result = Fraction{constant(Rational(1)), constant(Rational(1))};
509 while (n) {
510 if (n & 1) {
511 result = combine(std::move(*result), *base, false);
512 if (!result) { return std::nullopt; }
513 }
514 n >>= 1;
515 if (n) {
516 base = combine(*base, *base, false);
517 if (!base) { return std::nullopt; }
518 }
519 }
520 return result;
521 }
522 std::optional<Fraction> convert_number(const Node& node) {
524 if (!value) { return std::nullopt; }
526 value->get_numerator().bit_length() + value->get_denominator().bit_length(),
527 "residual.formal"))) { return std::nullopt; }
528 return Fraction{constant(*value), constant(Rational(1))};
529 }
530 std::optional<Fraction> convert_operands(const std::vector<Node>& operands, bool sum) {
531 std::optional<Fraction> result = Fraction{
532 constant(Rational(sum ? 0 : 1)), constant(Rational(1))};
533 for (const auto& child : operands) {
534 auto operand = convert(child);
535 if (!operand) { return std::nullopt; }
536 result = combine(std::move(*result), std::move(*operand), sum);
537 if (!result) { return std::nullopt; }
538 }
539 return result;
540 }
541 std::optional<Fraction> convert(const Node& node) {
542 if (!checked(context_.enter_recursion("residual.formal"))) { return std::nullopt; }
544 if (dynamic_cast<const NumberNode*>(node.get())) {
545 return convert_number(node);
546 }
547 for (const auto& atom : canonical_atoms_) {
548 if (atom.first->equals(*node)) {
549 return convert(atom.second);
550 }
551 }
552 for (std::size_t i = 0; i < atoms_.size(); ++i) {
553 if (!atoms_[i]->equals(*node)) { continue; }
554 Monomial powers(atoms_.size(), 0);
555 powers[i] = 1;
556 return Fraction{
557 MultiPoly({{std::move(powers), Rational(1)}}, variables_),
558 constant(Rational(1))};
559 }
560 auto sum = dynamic_cast<const AddNode*>(node.get());
561 auto product = dynamic_cast<const MultiplyNode*>(node.get());
562 if (sum || product) {
563 return convert_operands(
564 sum ? sum->operands() : product->operands(), sum != nullptr);
565 }
566 if (auto power = dynamic_cast<const PowerNode*>(node.get())) {
567 if (auto exponent = integer_power(*power)) {
569 }
570 }
571 return std::nullopt;
572 }
573 std::optional<MultiPoly> reduce_radicals(const MultiPoly& polynomial) {
574 if (!checked(context_.enter_recursion("residual.formal"))) { return std::nullopt; }
576 MultiPoly reduced(Rational(0), variables_);
577 for (auto term : polynomial.terms()) {
578 std::vector<std::pair<Node, int>> symbolic_squares;
579 for (std::size_t i = 0; i < radical_squares_.size(); ++i) {
580 if (term.first[i] < 2) { continue; }
581 const auto exponent = term.first[i] / 2;
582 if (radical_squares_[i]) {
583 const auto& square = *radical_squares_[i];
584 auto bits = term.second.get_numerator().bit_length() +
585 term.second.get_denominator().bit_length() +
586 static_cast<std::size_t>(exponent) *
587 (square.get_numerator().bit_length() +
588 square.get_denominator().bit_length());
590 bits, "residual.formal"))) {
591 return std::nullopt;
592 }
593 term.second =
594 term.second * square.power(BigInt(exponent));
595 } else if (radical_radicands_[i]) {
596 symbolic_squares.emplace_back(
598 } else {
599 continue;
600 }
602 static_cast<std::size_t>(exponent) + 1,
603 "residual.formal"))) {
604 return std::nullopt;
605 }
606 term.first[i] %= 2;
607 }
608 MultiPoly expanded({term}, variables_);
609 for (const auto& [radicand, exponent] : symbolic_squares) {
610 auto square = convert(radicand);
611 if (!square ||
612 square->denominator != constant(Rational(1))) {
613 return std::nullopt;
614 }
615 for (int power = 0; power < exponent; ++power) {
616 auto product = multiply(expanded, square->numerator);
617 if (!product) { return std::nullopt; }
618 expanded = std::move(*product);
619 }
620 }
622 static_cast<std::size_t>(reduced.num_terms()) +
623 static_cast<std::size_t>(expanded.num_terms()),
624 "residual.formal"))) {
625 return std::nullopt;
626 }
627 reduced = reduced + expanded;
628 }
629 return reduced;
630 }
631public:
633 : context_(budget.context()), budget_(budget) {}
634 Result<bool> prove(const Node& expression) {
635 AngleCollector angles(*this);
636 expression->accept(angles);
637 if (failure_) { return Result<bool>::failure(*failure_); }
638 if (!collect(expression))
640 auto fraction = convert(expression);
641 if (failure_) { return Result<bool>::failure(*failure_); }
642 if (!fraction) { return false; }
643 const bool has_radicals = std::any_of(
644 radical_squares_.begin(), radical_squares_.end(),
645 [](const auto& value) { return value.has_value(); }) ||
646 std::any_of(
648 [](const auto& value) { return value != nullptr; });
649 if (!has_radicals) {
650 return fraction->numerator.is_zero() &&
651 !fraction->denominator.is_zero();
652 }
653 auto numerator = reduce_radicals(fraction->numerator);
654 auto denominator = reduce_radicals(fraction->denominator);
655 if (failure_) { return Result<bool>::failure(*failure_); }
656 return numerator && denominator && numerator->is_zero() && !denominator->is_zero();
657 }
658};
659
660
661RootReplacementResult replace_exact_root(
662 const detail::SymbolicNodePtr& node, const detail::ExactRootId*& identity,
663 detail::RewriteBudget& budget, const std::string& operation);
664
666 const std::vector<detail::SymbolicNodePtr>& operands, bool sum,
667 const detail::ExactRootId*& identity, detail::RewriteBudget& budget, const std::string& operation) {
668 bool changed = false;
669 std::vector<detail::SymbolicNodePtr> replaced;
670 std::size_t nodes = 1;
671 for (auto operand = operands.begin(); operand != operands.end(); ++operand) {
672 auto child = replace_exact_root(*operand, identity, budget, operation);
673 if (!child || !child.value()) { return child; }
674 nodes = budget.append_size(nodes, budget.measure(*child.value()));
675 if (!changed && *child.value() != *operand) {
676 replaced.reserve(operands.size());
677 replaced.insert(replaced.end(), operands.begin(), operand);
678 changed = true;
679 }
680 if (changed) { replaced.push_back(std::move(*child.value())); }
681 }
682 if (!changed) { return RootReplacementResult::success(node); }
684 ? SymbolicFactory::create_add(std::move(replaced), &budget)
685 : SymbolicFactory::create_multiply(std::move(replaced), &budget));
686}
687
689 const PowerNode& power, const detail::ExactRootId*& identity,
690 detail::RewriteBudget& budget, const std::string& operation) {
691 auto base = replace_exact_root(power.base(), identity, budget, operation);
692 if (!base || !base.value()) { return base; }
693 auto exponent = replace_exact_root(power.exponent(), identity, budget, operation);
694 if (!exponent || !exponent.value()) { return exponent; }
695 if (*base.value() == power.base() && *exponent.value() == power.exponent()) {
697 }
698 budget.append_size(budget.append_size(1, budget.measure(*base.value())),
699 budget.measure(*exponent.value()));
701 SymbolicFactory::create_power(*base.value(), *exponent.value()));
702}
703
705 const detail::SymbolicNodePtr& node,
706 const detail::ExactRootId*& identity,
707 detail::RewriteBudget& budget,
708 const std::string& operation) {
709 auto& context = budget.context();
710 auto entered = context.enter_recursion(operation);
711 if (!entered) { return RootReplacementResult::failure(entered.error()); }
713 if (const auto* root = dynamic_cast<const RootOfNode*>(node.get())) {
714 if (identity && identity->compare(root->exact_id()) != 0) {
715 return RootReplacementResult::success(std::nullopt);
716 }
717 identity = &root->exact_id();
718 budget.require_nodes(1);
720 SymbolicFactory::create_variable("_residual_root"));
721 }
722 if (dynamic_cast<const NumberNode*>(node.get())) {
724 }
725 const auto* sum = dynamic_cast<const AddNode*>(node.get());
726 const auto* product = dynamic_cast<const MultiplyNode*>(node.get());
727 if (sum || product) {
728 const auto& operands = sum ? sum->operands() : product->operands();
729 return replace_root_operands(node, operands, sum != nullptr, identity, budget, operation);
730 }
731 if (const auto* power = dynamic_cast<const PowerNode*>(node.get())) {
732 return replace_root_power(node, *power, identity, budget, operation);
733 }
734 return RootReplacementResult::success(std::nullopt);
735}
736
738 const Rational& value, ComputationContext& context,
739 const std::string& operation) {
740 return context.require_integer_bits(std::max(
741 value.get_numerator().bit_length(),
742 value.get_denominator().bit_length()), operation);
743}
744
746 Polynomial<Rational> remainder,
747 const Polynomial<Rational>& defining,
749 const std::string& operation) {
750 auto terms = context.require_expansion_terms(
751 std::max(remainder.coeffs.size(), defining.coeffs.size()), operation);
752 if (!terms) return Result<bool>::failure(terms.error());
753 for (const auto& coefficient : remainder.coeffs) {
754 auto checked = check_residual_coefficient(coefficient, context, operation);
755 if (!checked) return Result<bool>::failure(checked.error());
756 }
757 for (const auto& coefficient : defining.coeffs) {
758 auto checked = check_residual_coefficient(coefficient, context, operation);
759 if (!checked) return Result<bool>::failure(checked.error());
760 }
761 while (remainder.degree() >= defining.degree()) {
762 const auto offset = remainder.coeffs.size() - defining.coeffs.size();
763 const auto& factor = remainder.coeffs.back();
764 for (std::size_t index = 0; index + 1 < defining.coeffs.size(); ++index) {
765 auto step = context.consume_steps(1, operation);
766 if (!step) return Result<bool>::failure(step.error());
767 auto product = factor * defining.coeffs[index];
768 auto checked = check_residual_coefficient(product, context, operation);
769 if (!checked) return Result<bool>::failure(checked.error());
770 auto& coefficient = remainder.coeffs[offset + index];
771 coefficient = coefficient - product;
772 checked = check_residual_coefficient(coefficient, context, operation);
773 if (!checked) return Result<bool>::failure(checked.error());
774 }
775 remainder.coeffs.pop_back();
776 remainder.trim();
777 }
778 return Result<bool>::success(remainder.is_zero());
779}
780
782 const ExprPtr& expression, detail::RewriteBudget& budget,
783 const std::string& operation) {
784 auto& context = budget.context();
785 const detail::ExactRootId* identity = nullptr;
786 auto replaced = replace_exact_root(
787 detail::node(expression), identity, budget, operation);
788 if (!replaced) return ResidualCheckResult::failure(replaced.error());
789 if (!replaced.value() || !identity) {
791 }
792 auto polynomial = recognize_rational_polynomial(
793 *detail::make_expression_ptr(*replaced.value()),
794 "_residual_root", context);
795 if (!polynomial) return ResidualCheckResult::failure(polynomial.error());
796 if (polynomial.value()) {
797 auto zero = has_zero_root_remainder(
798 std::move(*polynomial.value()), identity->polynomial, context, operation);
799 if (!zero) return ResidualCheckResult::failure(zero.error());
800 if (zero.value()) {
802 ExactResidualProof{expression}});
803 }
804 }
806}
807
808
810 const ExprPtr& expression,
811 detail::RewriteBudget& budget,
812 const std::string& operation) {
813 auto& context = budget.context();
814 auto root_proof = prove_exact_root_residual(expression, budget, operation);
815 if (!root_proof ||
816 std::holds_alternative<ProvedZeroResidual>(root_proof.value())) {
817 return root_proof;
818 }
819 auto proof = detail::classify_exact_zero(
820 expression, context, operation);
821 if (!proof) { return ResidualCheckResult::failure(proof.error()); }
822 if (proof.value() == detail::ZeroProof::Zero) {
824 ExactNormalizationProof{expression}});
825 }
826 const auto variables = free_variables(detail::node(expression), &budget);
827 if (variables.size() == 1) {
828 auto polynomial = recognize_rational_polynomial(
829 *expression, *variables.begin(), context);
830 if (!polynomial) {
831 return ResidualCheckResult::failure(polynomial.error());
832 }
833 if (polynomial.value() && !polynomial.value()->is_zero()) {
835 ProvedNonIdentityResidual{expression});
836 }
837 }
838 if (proof.value() == detail::ZeroProof::NonZero) {
840 ProvedNonIdentityResidual{expression});
841 }
843}
844
845bool is_decided(const ResidualCheck& result) {
846 return !std::holds_alternative<UnprovedResidual>(result);
847}
848
850 bool trig = false;
851 bool exp_log = false;
852};
853
855 const detail::SymbolicNodePtr& node, ResidualProfiles& profiles);
856
858 const std::vector<detail::SymbolicNodePtr>& children,
859 ResidualProfiles& profiles) {
860 for (const auto& child : children) {
861 collect_residual_profiles(child, profiles);
862 }
863}
864
866 const FunctionNode& function, ResidualProfiles& profiles) {
867 profiles.trig = profiles.trig ||
870 profiles.exp_log = profiles.exp_log ||
873 collect_residual_profile_children(function.arguments(), profiles);
874}
875
877 const detail::SymbolicNodePtr& node, ResidualProfiles& profiles) {
878 if (!node || (profiles.trig && profiles.exp_log)) {
879 return;
880 }
881 if (const auto function =
882 std::dynamic_pointer_cast<const FunctionNode>(node)) {
884 return;
885 }
886 if (const auto add = std::dynamic_pointer_cast<const AddNode>(node)) {
887 collect_residual_profile_children(add->operands(), profiles);
888 return;
889 }
890 if (const auto multiply =
891 std::dynamic_pointer_cast<const MultiplyNode>(node)) {
892 collect_residual_profile_children(multiply->operands(), profiles);
893 return;
894 }
895 if (const auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
896 collect_residual_profiles(power->base(), profiles);
897 collect_residual_profiles(power->exponent(), profiles);
898 return;
899 }
900 if (const auto complex =
901 std::dynamic_pointer_cast<const ComplexNode>(node)) {
902 collect_residual_profiles(complex->real(), profiles);
903 collect_residual_profiles(complex->imag(), profiles);
904 }
905}
906
908 const ExprPtr& current, ComputationContext& context,
909 const EqvOptions& options) {
910 ResidualProfiles required;
911 collect_residual_profiles(detail::node(current), required);
912 const auto zero = SymbolicExpr::number(0);
913 for (const auto profile : {
915 if ((profile == EqvProfile::TrigBasic && !required.trig) ||
916 (profile == EqvProfile::ExpLogBasic && !required.exp_log)) {
917 continue;
918 }
919 auto profile_options = options;
920 profile_options.profile = profile;
921 auto equivalent = detail::equivalent_impl(*current, *zero, context, profile_options);
922 if (!equivalent) {
925 continue;
926 }
928 }
929 if (equivalent.value()) {
931 ExactResidualProof{current}});
932 }
933 }
935}
936
938 detail::RewriteBudget& budget, const EqvOptions& options, const std::string& operation) {
939 auto& context = budget.context();
940 std::optional<detail::AssumptionFacts> assumed;
941 if (context.assumptions()) { assumed.emplace(*context.assumptions()); }
942 const auto& facts = assumed
943 ? static_cast<const FactsQuery&>(*assumed) : detail::no_facts();
944 auto current = detail::make_expression_ptr(
945 detail::normalize_node(detail::node(residual), budget, facts));
946 auto first = classify_residual(current, budget, operation);
947 if (!first || is_decided(first.value())) { return first; }
949 detail::expand_node(detail::node(current), budget, facts));
950 auto expanded = classify_residual(current, budget, operation);
951 if (!expanded || is_decided(expanded.value())) { return expanded; }
952 /**
953 * @brief 通分证明受计算预算约束,替代符号系数的 Euclid 及因子搜索。
954 * 支持绑定整数名同时出现在参数分母中的解族。
955 */
956 auto formal = FormalResidual(budget).prove(detail::node(current));
957 if (!formal) { return ResidualCheckResult::failure(formal.error()); }
958 if (formal.value()) {
960 }
961 return prove_with_profiles(current, context, options);
962}
963
965 const detail::SymbolicNodePtr& node, detail::RewriteBudget& budget) {
966 detail::RewriteScope scope(&budget);
967 if (const auto* piecewise = dynamic_cast<const PiecewiseNode*>(node.get())) {
968 return piecewise;
969 }
970 const auto* add = dynamic_cast<const AddNode*>(node.get());
971 const auto* mul = dynamic_cast<const MultiplyNode*>(node.get());
972 if (add || mul) {
973 for (const auto& child : add ? add->operands() : mul->operands()) {
974 if (auto piecewise = residual_piecewise(child, budget)) { return piecewise; }
975 }
976 }
977 if (const auto* power = dynamic_cast<const PowerNode*>(node.get())) {
978 return residual_piecewise(power->base(), budget);
979 }
980 return nullptr;
981}
982
985 std::size_t branch_;
986public:
987 ResidualBranch(const PiecewiseNode& selected, std::size_t branch,
988 detail::RewriteBudget& budget)
989 : SymbolicRewriter(&budget), selected_(selected), branch_(branch) {}
990 void visit(const PiecewiseNode& node) override {
991 if (node.branches().size() != selected_.branches().size() ||
992 static_cast<bool>(node.default_expr()) !=
993 static_cast<bool>(selected_.default_expr())) {
995 return;
996 }
997 for (std::size_t index = 0; index < node.branches().size(); ++index) {
998 if (!node.branches()[index].condition->equals(
999 *selected_.branches()[index].condition)) {
1001 return;
1002 }
1003 }
1004 set_result(branch_ == node.branches().size()
1005 ? node.default_expr() : node.branches()[branch_].expression);
1006 }
1007};
1008
1009std::optional<ResidualCheckResult> prove_piecewise_residual(
1010 const ExprPtr& residual, detail::RewriteBudget& budget,
1011 const EqvOptions& options) {
1012 const auto* piecewise = residual_piecewise(detail::node(residual), budget);
1013 if (!piecewise) { return std::nullopt; }
1014 auto& context = budget.context();
1015 auto entered = context.enter_recursion("residual.piecewise");
1016 if (!entered) { return ResidualCheckResult::failure(entered.error()); }
1018 const auto count = piecewise->branches().size() +
1019 static_cast<std::size_t>(piecewise->default_expr() != nullptr);
1020 for (std::size_t index = 0; index < count; ++index) {
1021 ResidualBranch select(*piecewise, index, budget);
1022 auto branch = select.rewrite(detail::node(residual));
1023 budget.measure(branch);
1025 if (!proof) { return proof; }
1026 if (!std::holds_alternative<ProvedZeroResidual>(proof.value())) {
1028 }
1029 }
1032}
1033
1034}
1035
1037 const ExprPtr& residual,
1039 const LMCAS::EqvOptions& options) {
1040 constexpr const char* operation = "residual.check_zero";
1041 if (!residual || !detail::node(residual)) {
1044 "residual expression cannot be null", operation);
1045 }
1046 auto access = context.consume_steps(1, operation);
1047 if (!access) { return ResidualCheckResult::failure(access.error()); }
1048
1049 try {
1050 detail::RewriteBudget budget(context, context.limits().max_recursion_depth,
1051 context.limits().max_ast_nodes, operation);
1052 budget.measure(detail::node(residual));
1053 if (auto piecewise = prove_piecewise_residual(residual, budget, options)) {
1054 return std::move(*piecewise);
1055 }
1056 auto root_proof = prove_exact_root_residual(residual, budget, operation);
1057 if (!root_proof ||
1058 std::holds_alternative<ProvedZeroResidual>(root_proof.value())) {
1059 return root_proof;
1060 }
1061 auto formal = FormalResidual(budget).prove(detail::node(residual));
1062 if (!formal) { return ResidualCheckResult::failure(formal.error()); }
1063 if (formal.value())
1065 return prove_normalized_residual(residual, budget, options, operation);
1066 } catch (const CasError& error) {
1067 return ResidualCheckResult::failure(error);
1068 } catch (const std::length_error& error) {
1069 return ResidualCheckResult::failure(CasErrc::ResourceLimit, error.what(), operation);
1070 } catch (const std::bad_alloc&) {
1073 "residual verification allocation failed", operation);
1074 } catch (const std::exception& error) {
1076 CasErrc::InternalInvariant, error.what(), operation);
1077 }
1078}
1079
1080
1081}
加法节点,表示多个操作数的求和。
const std::shared_ptr< const AssumptionContext > & assumptions() const noexcept
Result< void > consume_steps(std::size_t amount=1, const std::string &operation={})
Result< void > enter_recursion(const std::string &operation={})
Result< void > require_integer_bits(std::size_t amount, const std::string &operation={}) const
Result< void > require_expansion_terms(std::size_t amount, const std::string &operation={}) const
函数节点,表示数学函数调用(三角函数、对数、特殊函数等)。
FuncType type() const noexcept
@ Sqrt
对数、绝对值、平方根。
const std::vector< std::shared_ptr< const SymbolicNode > > & arguments() const noexcept
乘法节点,表示多个操作数的乘积。
分段函数节点,表示条件分支表达式.
const std::shared_ptr< const SymbolicNode > & default_expr() const noexcept
const std::vector< Branch > & branches() const noexcept
一元多项式模板类
bool is_zero() const
判断是否为零多项式
std::vector< CoeffType > coeffs
系数数组,coeffs[i] 为 x^i 的系数
int degree() const
获取多项式的次数
void trim()
去除高次零系数
幂运算节点,表示 base^exponent。
const std::shared_ptr< const SymbolicNode > & exponent() const noexcept
const std::shared_ptr< const SymbolicNode > & base() const noexcept
CasError & error()
static Result failure(CasErrc code, std::string message, std::string operation={})
static Result success(T value)
static std::shared_ptr< const SymbolicNode > create_add(std::vector< std::shared_ptr< const SymbolicNode > > ops, detail::RewriteBudget *budget=nullptr)
创建加法节点,自动扁平化嵌套加法并消除零项.
static std::shared_ptr< const SymbolicNode > create_variable(const std::string &name)
static std::shared_ptr< const SymbolicNode > create_power(std::shared_ptr< const SymbolicNode > base, std::shared_ptr< const SymbolicNode > exponent)
创建幂运算节点,自动处理指数为 0/1 及底数为 0/1 的情况.
static std::shared_ptr< const SymbolicNode > create_multiply(std::vector< std::shared_ptr< const SymbolicNode > > ops, detail::RewriteBudget *budget=nullptr)
创建乘法节点,自动扁平化嵌套乘法并消除单位元.
static std::shared_ptr< const SymbolicNode > create_number(const ::LMCAS::BigInt &v)
变量节点,表示一个符号变量。
在原公共定义域上证明形式有理恒等式。 将不透明标量子式视为不定元,保留其点值可能为零的情况; 交叉相乘免去因式分解及符号系数 GCD。
std::optional< Fraction > convert_operands(const std::vector< Node > &operands, bool sum)
std::optional< MultiPoly > multiply(const MultiPoly &a, const MultiPoly &b)
std::optional< Fraction > convert_integer_power(const PowerNode &power, std::int64_t n)
std::optional< bool > collect_integer_power(const PowerNode &power, bool canonicalize_radicals)
bool register_regular_atom(const Node &node, std::optional< Rational > square)
std::optional< MultiPoly > reduce_radicals(const MultiPoly &polynomial)
bool collect_operands(const std::vector< Node > &operands, bool canonicalize_radicals)
std::optional< Fraction > combine(Fraction a, Fraction b, bool sum)
std::int64_t double_angle_multiple(const Rational &scale, const Node &base, bool hyperbolic)
std::optional< bool > register_canonical_radical(const Node &node, const Rational &square)
ResidualBranch(const PiecewiseNode &selected, std::size_t branch, detail::RewriteBudget &budget)
ComputationContext & context() const noexcept
std::size_t measure(const std::shared_ptr< const SymbolicNode > &node)
void require_nodes(std::size_t nodes) const
std::size_t append_size(std::size_t current, std::size_t child)
void set_result(SymbolicNodePtr result)
SymbolicNodePtr rewrite(const SymbolicNodePtr &node)
SymbolicNodePtr current() const noexcept
精确实根式及根式、pi、e 的有理组合。
多元多项式类 MultiPoly,稀疏表示,支持任意数量变量上的环运算。
RootReplacementResult replace_root_power(const detail::SymbolicNodePtr &node, const PowerNode &power, const detail::ExactRootId *&identity, detail::RewriteBudget &budget, const std::string &operation)
void collect_residual_profile_children(const std::vector< detail::SymbolicNodePtr > &children, ResidualProfiles &profiles)
ResidualCheckResult prove_exact_root_residual(const ExprPtr &expression, detail::RewriteBudget &budget, const std::string &operation)
void collect_function_profile(const FunctionNode &function, ResidualProfiles &profiles)
ResidualCheckResult prove_with_profiles(const ExprPtr &current, ComputationContext &context, const EqvOptions &options)
RootReplacementResult replace_root_operands(const detail::SymbolicNodePtr &node, const std::vector< detail::SymbolicNodePtr > &operands, bool sum, const detail::ExactRootId *&identity, detail::RewriteBudget &budget, const std::string &operation)
RootReplacementResult replace_exact_root(const detail::SymbolicNodePtr &node, const detail::ExactRootId *&identity, detail::RewriteBudget &budget, const std::string &operation)
ResidualCheckResult classify_residual(const ExprPtr &expression, detail::RewriteBudget &budget, const std::string &operation)
Result< bool > has_zero_root_remainder(Polynomial< Rational > remainder, const Polynomial< Rational > &defining, ComputationContext &context, const std::string &operation)
const PiecewiseNode * residual_piecewise(const detail::SymbolicNodePtr &node, detail::RewriteBudget &budget)
void collect_residual_profiles(const detail::SymbolicNodePtr &node, ResidualProfiles &profiles)
Result< std::optional< detail::SymbolicNodePtr > > RootReplacementResult
Result< void > check_residual_coefficient(const Rational &value, ComputationContext &context, const std::string &operation)
ResidualCheckResult prove_normalized_residual(const ExprPtr &residual, detail::RewriteBudget &budget, const EqvOptions &options, const std::string &operation)
std::optional< ResidualCheckResult > prove_piecewise_residual(const ExprPtr &residual, detail::RewriteBudget &budget, const EqvOptions &options)
SymbolicNodePtr expand_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
Result< ZeroProof > classify_exact_zero(const ExprPtr &expression, ComputationContext &context, const std::string &operation)
std::shared_ptr<::LMCAS::SymbolicExpr > make_expression_ptr(SymbolicNodePtr root)
const SymbolicNodePtr & node(const ::LMCAS::SymbolicExpr &expression) noexcept
std::shared_ptr< const SymbolicNode > SymbolicNodePtr
const FactsQuery & no_facts() noexcept
std::shared_ptr< const Node > make_node(Args &&... args)
std::optional< Rational > exact_rational_value(const NumberNode &number)
Result< Tribool > query_real_value(const Node &node, const FactsQuery &facts, ComputationContext &context)
SymbolicNodePtr normalize_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
Result< bool > equivalent_impl(const SymbolicExpr &lhs, const SymbolicExpr &rhs, ComputationContext &context, const EqvOptions &options)
std::vector< int > Monomial
单项式类型,以整数向量表示各变量的指数
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
ExprResult integer(Integer)=delete
bool try_get_integer_value(const std::shared_ptr< const NumberNode > &node, BigInt &value)
ExprResult real(const ExprPtr &expression, ComputationContext &context)
ExprResult mul(const ExprPtr &lhs, const ExprPtr &rhs, ComputationContext &context)
const std::string ComputationContext const SolveOptions & options
@ True
Property definitely holds
ExprResult add(const ExprPtr &lhs, const ExprPtr &rhs, ComputationContext &context)
Result< bool > equivalent(const SymbolicExpr &lhs, const SymbolicExpr &rhs, ComputationContext &context, const EqvOptions &options)
const MultiPoly & b
Result< ResidualCheck > ResidualCheckResult
ExprResult complex(ExprPtr real, ExprPtr imag)
const ExprPtr & exponent
__attribute__((visibility("default"))) VectorCalculusExprResult curve_integral_scalar_checked(const std __attribute__((visibility("default"))) VectorCalculusExprResult curve_integral_scalar_checked(const std __attribute__((visibility("default"))) std const VectorField const std::string const std::shared_ptr< SymbolicExpr > & a
void normalization_check_children(detail::RewriteBudget *budget, std::size_t roots, const std::shared_ptr< const Nodes > &... children)
std::set< std::string > free_variables(const detail::SymbolicNodePtr &expression, detail::RewriteBudget *budget)
std::shared_ptr< SymbolicExpr > ExprPtr
std::variant< ProvedZeroResidual, ProvedNonIdentityResidual, UnprovedResidual > ResidualCheck
Result< OptionalRationalPolynomial > recognize_rational_polynomial(const SymbolicExpr &expression, const std::string &variable, ComputationContext &context)
const ExprPtr & right
ResidualCheckResult check_zero_residual(const ExprPtr &residual, ComputationContext &context, const LMCAS::EqvOptions &options)
const std::string const ExprPtr & value
const ExprPtr & denominator
ExprResult function(const std::string &name, std::vector< ExprPtr > arguments)
在表达式的原公共定义域上认证值相等;点值定义域与分母条件需另行证明。
证明残差在原公共定义域上非恒零,例如非零有理多项式。 残差仍可能在特定参数值处为零;点值不等性、定义域及上下文假设下的非零性均需另行证明。
多项式转换与精确符号多项式代数。
Polynomial< Rational > polynomial
int compare(const ExactRootId &other) const