64 if (result)
return true;
76 const std::vector<Node>& operands,
bool canonicalize_radicals) {
77 for (
const auto& child : operands) {
78 if (!
collect(child, canonicalize_radicals)) {
90 if (*exponent < -1000 || *exponent > 1000) {
95 bool collect(
const Node& node,
bool canonicalize_radicals =
true) {
103 for (
const auto& atom :
atoms_) {
104 if (atom->equals(*node)) {
108 if (
dynamic_cast<const NumberNode*
>(node.get())) {
111 if (
auto sum =
dynamic_cast<const AddNode*
>(node.get())) {
113 sum->operands(), canonicalize_radicals);
117 product->operands(), canonicalize_radicals);
131 function->arguments().size() == 1) {
134 }
else if (
auto power =
dynamic_cast<const PowerNode*
>(node.get())) {
137 return power->base();
167 const auto* fn =
dynamic_cast<const FunctionNode*
>(node.get());
168 if (!fn || fn->arguments().size() != 1) {
return nullptr; }
170 const auto& argument = fn->arguments()[0];
171 if (fn->type() == FT::Cos) {
175 if (fn->type() == FT::Cosh) {
178 if (fn->type() == FT::Abs) {
179 std::optional<detail::AssumptionFacts> assumed;
181 const auto& facts = assumed
191 if (!
collect(replacement)) {
return false; }
201 auto value = Rational(1);
203 if (!
mul) {
return {
value, argument}; }
204 std::vector<Node> factors;
205 for (
const auto& child :
mul->operands()) {
207 else { factors.push_back(child); }
213 const bool hyperbolic = fn.
type() == FT::Sinh || fn.
type() == FT::Cosh ||
214 fn.
type() == FT::Tanh;
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; }
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; }
229 "residual.formal"))) {
return; }
231 angles_.push_back({std::move(base), std::move(scale), hyperbolic});
240 RecursiveSymbolicVisitor::visit(fn);
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();
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; }
268 if (scale < Rational(0)) { scale = -scale; }
269 if (scale.is_zero()) {
return nullptr; }
270 if (scale != Rational(1)) {
274 if (scale == Rational(1) && canonical->equals(*radicand)) {
return nullptr; }
278 if (scale == Rational(1)) {
return radical; }
284 if (fn.
arguments().size() != 1) {
return nullptr; }
286 const auto& argument = fn.
arguments()[0];
287 if (fn.
type() == FT::Sqrt) {
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);
298 return power(fn.
type() == FT::Sec ? cosine : sine, -1);
300 if (fn.
type() == FT::Tanh) {
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; }
308 if (!canonical->equals(*argument)) {
return function(fn.
type(), canonical); }
309 if (fn.
type() == FT::Exp) {
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));
317 return product(std::move(factors));
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) {
327 if (scale < Rational(0)) {
333 const bool odd = fn.
type() == FT::Sin || fn.
type() == FT::Sinh;
334 if (scale < Rational(0)) {
339 const bool hyperbolic = fn.
type() == FT::Sinh || fn.
type() == FT::Cosh;
342 auto sine =
function(hyperbolic ? FT::Sinh : FT::Sin, half);
343 auto cosine =
function(hyperbolic ? FT::Cosh : FT::Cos, half);
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; } }
357 {
if (atom.first->equals(*node)) {
return true; } }
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"))) {
369 auto root = Irrational::sqrt_checked(
376 ? root.value().to_symbolic()
377 : (root.value() * Rational(BigInt(1),
denominator)).to_symbolic());
378 if (canonical->equals(*node)) {
381 if (!
collect(canonical,
false) ||
384 "residual.formal"))) {
393 const Node& node, std::optional<Rational> square) {
395 if (radicand && !square && !
collect(radicand)) {
399 if (radicand && !square) {
404 "residual.formal"))) {
409 "_formal" + std::to_string(
variables_.size()));
417 if (
const auto* fn =
dynamic_cast<const FunctionNode*
>(node.get())) {
422 if (
const auto* exponentiation =
dynamic_cast<const PowerNode*
>(node.get())) {
426 auto degree =
exponent->get_numerator().try_to_int64();
427 if (degree && *degree >= -1000 && *degree <= 1000) {
439 if (canonicalize_radicals && square &&
440 !square->get_numerator().is_negative()) {
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());
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;
462 a_degree = std::max(a_degree, degree);
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;
469 b_degree = std::max(b_degree, degree);
471 if (a_degree + b_degree > std::numeric_limits<int>::max()) {
473 "formal polynomial degree exceeds its representation",
"residual.formal"));
477 (a_bits + b_bits + 1) * (count + 1),
"residual.formal")))
return std::nullopt;
484 auto numerator =
multiply(
a.numerator,
b.numerator);
485 if (!numerator)
return std::nullopt;
488 auto left =
multiply(
a.numerator,
b.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;
506 if (!base) {
return std::nullopt; }
507 if (n < 0) { std::swap(base->numerator, base->denominator); n = -n; }
511 result =
combine(std::move(*result), *base,
false);
512 if (!result) {
return std::nullopt; }
516 base =
combine(*base, *base,
false);
517 if (!base) {
return std::nullopt; }
524 if (!
value) {
return std::nullopt; }
526 value->get_numerator().bit_length() +
value->get_denominator().bit_length(),
527 "residual.formal"))) {
return std::nullopt; }
531 std::optional<Fraction> result =
Fraction{
533 for (
const auto& child : operands) {
535 if (!operand) {
return std::nullopt; }
536 result =
combine(std::move(*result), std::move(*operand),
sum);
537 if (!result) {
return std::nullopt; }
544 if (
dynamic_cast<const NumberNode*
>(node.get())) {
548 if (atom.first->equals(*node)) {
552 for (std::size_t i = 0; i <
atoms_.size(); ++i) {
553 if (!
atoms_[i]->equals(*node)) {
continue; }
557 MultiPoly({{std::move(powers), Rational(1)}},
variables_),
560 auto sum =
dynamic_cast<const AddNode*
>(node.get());
577 for (
auto term : polynomial.terms()) {
578 std::vector<std::pair<Node, int>> symbolic_squares;
580 if (term.first[i] < 2) {
continue; }
581 const auto exponent = term.first[i] / 2;
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"))) {
594 term.second * square.power(BigInt(
exponent));
596 symbolic_squares.emplace_back(
602 static_cast<std::size_t
>(
exponent) + 1,
603 "residual.formal"))) {
609 for (
const auto& [radicand,
exponent] : symbolic_squares) {
610 auto square =
convert(radicand);
612 square->denominator !=
constant(Rational(1))) {
617 if (!
product) {
return std::nullopt; }
618 expanded = std::move(*
product);
622 static_cast<std::size_t
>(reduced.num_terms()) +
623 static_cast<std::size_t
>(expanded.num_terms()),
624 "residual.formal"))) {
627 reduced = reduced + expanded;
636 expression->accept(angles);
640 auto fraction =
convert(expression);
642 if (!fraction) {
return false; }
643 const bool has_radicals = std::any_of(
645 [](
const auto&
value) { return value.has_value(); }) ||
648 [](
const auto&
value) { return value != nullptr; });
650 return fraction->numerator.is_zero() &&
651 !fraction->denominator.is_zero();
661RootReplacementResult replace_exact_root(
666 const std::vector<detail::SymbolicNodePtr>& operands,
bool sum,
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) {
673 if (!child || !child.value()) {
return child; }
675 if (!changed && *child.value() != *operand) {
676 replaced.reserve(operands.size());
677 replaced.insert(replaced.end(), operands.begin(), operand);
680 if (changed) { replaced.push_back(std::move(*child.value())); }
692 if (!base || !base.value()) {
return base; }
708 const std::string& operation) {
710 auto entered =
context.enter_recursion(operation);
713 if (
const auto* root =
dynamic_cast<const RootOfNode*
>(node.get())) {
714 if (identity && identity->
compare(root->exact_id()) != 0) {
717 identity = &root->exact_id();
722 if (
dynamic_cast<const NumberNode*
>(node.get())) {
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();
731 if (
const auto* power =
dynamic_cast<const PowerNode*
>(node.get())) {
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);
749 const std::string& operation) {
750 auto terms =
context.require_expansion_terms(
751 std::max(remainder.
coeffs.size(), defining.
coeffs.size()), operation);
753 for (
const auto& coefficient : remainder.
coeffs) {
757 for (
const auto& coefficient : defining.
coeffs) {
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);
767 auto product = factor * defining.
coeffs[index];
770 auto& coefficient = remainder.
coeffs[offset + index];
771 coefficient = coefficient - product;
775 remainder.
coeffs.pop_back();
783 const std::string& operation) {
789 if (!replaced.value() || !identity) {
796 if (polynomial.value()) {
812 const std::string& operation) {
816 std::holds_alternative<ProvedZeroResidual>(root_proof.value())) {
820 expression,
context, operation);
827 if (variables.size() == 1) {
829 *expression, *variables.begin(),
context);
833 if (polynomial.value() && !polynomial.value()->is_zero()) {
846 return !std::holds_alternative<UnprovedResidual>(result);
858 const std::vector<detail::SymbolicNodePtr>& children,
860 for (
const auto& child : children) {
878 if (!node || (profiles.
trig && profiles.
exp_log)) {
882 std::dynamic_pointer_cast<const FunctionNode>(node)) {
886 if (
const auto add = std::dynamic_pointer_cast<const AddNode>(node)) {
890 if (
const auto multiply =
891 std::dynamic_pointer_cast<const MultiplyNode>(node)) {
895 if (
const auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
901 std::dynamic_pointer_cast<const ComplexNode>(node)) {
912 const auto zero = SymbolicExpr::number(0);
913 for (
const auto profile : {
919 auto profile_options =
options;
920 profile_options.profile = profile;
940 std::optional<detail::AssumptionFacts> assumed;
941 if (
context.assumptions()) { assumed.emplace(*
context.assumptions()); }
942 const auto& facts = assumed
947 if (!first ||
is_decided(first.value())) {
return first; }
951 if (!expanded ||
is_decided(expanded.value())) {
return expanded; }
958 if (formal.value()) {
967 if (
const auto* piecewise =
dynamic_cast<const PiecewiseNode*
>(node.get())) {
970 const auto*
add =
dynamic_cast<const AddNode*
>(node.get());
973 for (
const auto& child :
add ?
add->operands() :
mul->operands()) {
977 if (
const auto* power =
dynamic_cast<const PowerNode*
>(node.get())) {
992 static_cast<bool>(node.default_expr()) !=
997 for (std::size_t index = 0; index < node.branches().size(); ++index) {
998 if (!node.branches()[index].condition->equals(
1005 ? node.default_expr() : node.branches()[
branch_].expression);
1013 if (!piecewise) {
return std::nullopt; }
1015 auto entered =
context.enter_recursion(
"residual.piecewise");
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) {
1025 if (!proof) {
return proof; }
1026 if (!std::holds_alternative<ProvedZeroResidual>(proof.value())) {
1040 constexpr const char* operation =
"residual.check_zero";
1044 "residual expression cannot be null", operation);
1046 auto access =
context.consume_steps(1, operation);
1051 context.limits().max_ast_nodes, operation);
1053 if (
auto piecewise = prove_piecewise_residual(residual, budget,
options)) {
1054 return std::move(*piecewise);
1056 auto root_proof = prove_exact_root_residual(residual, budget, operation);
1058 std::holds_alternative<ProvedZeroResidual>(root_proof.value())) {
1061 auto formal = FormalResidual(budget).prove(
detail::node(residual));
1065 return prove_normalized_residual(residual, budget,
options, operation);
1068 }
catch (
const std::length_error& error) {
1070 }
catch (
const std::bad_alloc&) {
1073 "residual verification allocation failed", operation);
1074 }
catch (
const std::exception& error) {
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
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
获取多项式的次数
const std::shared_ptr< const SymbolicNode > & exponent() const noexcept
const std::shared_ptr< const SymbolicNode > & base() const noexcept
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)
const PiecewiseNode & selected_
ResidualBranch(const PiecewiseNode &selected, std::size_t branch, detail::RewriteBudget &budget)
void visit(const PiecewiseNode &node) override
ComputationContext & context_
ResidualRecursionScope(ComputationContext &context)
~ResidualRecursionScope()
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
多元多项式类 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 ¤t, 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)
bool is_decided(const ResidualCheck &result)
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)
Result< ResidualCheck > ResidualCheckResult
ExprResult complex(ExprPtr real, ExprPtr imag)
__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)
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