LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
symbolic_gcd.cpp
浏览该文件的文档.
1#include "poly_utils.hpp"
2
9
10#include <algorithm>
11#include <cstdint>
12#include <exception>
13#include <limits>
14#include <memory>
15#include <new>
16#include <set>
17#include <string>
18#include <utility>
19#include <vector>
20
21namespace LMCAS {
22namespace {
23
24constexpr const char* kOperation = "symbolic_polynomial_gcd";
25
27public:
29 ~RecursionFrame() { context_.leave_recursion(); }
30
33
34private:
36};
37
39 const std::shared_ptr<const SymbolicNode>& node,
40 std::set<std::string>& variables,
42
44 const std::vector<std::shared_ptr<const SymbolicNode>>& operands,
45 std::set<std::string>& variables, ComputationContext& context) {
46 for (const auto& operand : operands) {
47 auto result = collect_polynomial_variables(operand, variables, context);
48 if (!result) { return result; }
49 }
50 return Result<void>::success();
51}
52
54 const std::shared_ptr<const SymbolicNode>& node,
55 std::set<std::string>& variables,
57 auto entered = context.enter_recursion(kOperation);
58 if (!entered) { return entered; }
60
61 if (!node) {
63 CasErrc::InternalInvariant, "polynomial contains a null node",
65 }
66 if (std::dynamic_pointer_cast<const NumberNode>(node)) {
67 return Result<void>::success();
68 }
69 if (auto variable = std::dynamic_pointer_cast<const VariableNode>(node)) {
70 if (variable->is_constant()) {
72 "mathematical constants are not rational polynomial variables",
74 }
75 variables.insert(variable->name());
76 return Result<void>::success();
77 }
78 if (auto addition = std::dynamic_pointer_cast<const AddNode>(node)) {
79 return collect_polynomial_operands(addition->operands(), variables, context);
80 }
81 if (auto product = std::dynamic_pointer_cast<const MultiplyNode>(node)) {
82 return collect_polynomial_operands(product->operands(), variables, context);
83 }
84 if (auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
85 auto exponent = std::dynamic_pointer_cast<const NumberNode>(power->exponent());
86 if (!exponent) {
89 "polynomial exponents must be exact nonnegative integers",
91 }
92 return collect_polynomial_variables(power->base(), variables, context);
93 }
96 "expression is outside the exact rational polynomial domain",
98}
99
101 const std::shared_ptr<const SymbolicNode>& node) {
102 auto number = std::dynamic_pointer_cast<const NumberNode>(node);
103 if (!number) {
106 "polynomial exponents must be exact nonnegative integers",
107 kOperation);
108 }
109
110 BigInt integer;
111 if (std::holds_alternative<BigInt>(number->value())) {
112 integer = std::get<BigInt>(number->value());
113 } else {
114 const Rational& rational = std::get<Rational>(number->value());
115 if (!rational.is_integer()) {
118 "polynomial exponents must be integers", kOperation);
119 }
120 integer = rational.to_bigint();
121 }
122
123 const auto value = integer.try_to_int64();
124 if (!value || *value < 0) {
127 "polynomial exponents must be representable nonnegative integers",
128 kOperation);
129 }
130 return Result<std::size_t>::success(static_cast<std::size_t>(*value));
131}
132
133Result<void> check_term_budget(std::size_t lhs_terms,
134 std::size_t rhs_terms,
136 const std::size_t limit = context.limits().max_expansion_terms;
137 if (lhs_terms != 0 && rhs_terms > limit / lhs_terms) {
140 "polynomial expansion term budget exhausted", kOperation);
141 }
142 return context.consume_steps(lhs_terms * rhs_terms + 1, kOperation);
143}
144
146 const MultiPoly& rhs,
148 auto budget = check_term_budget(
149 static_cast<std::size_t>(lhs.num_terms()),
150 static_cast<std::size_t>(rhs.num_terms()), context);
151 if (!budget) { return Result<MultiPoly>::failure(budget.error()); }
152 MultiPoly result = lhs * rhs;
153 if (static_cast<std::size_t>(result.num_terms()) >
154 context.limits().max_expansion_terms) {
157 "polynomial expansion term budget exhausted", kOperation);
158 }
159 return Result<MultiPoly>::success(std::move(result));
160}
161
162Result<MultiPoly> node_to_multivariate_polynomial(
163 const std::shared_ptr<const SymbolicNode>& node,
164 const std::vector<std::string>& variables,
166
168 const std::shared_ptr<const AddNode>& addition,
169 const std::vector<std::string>& variables, ComputationContext& context) {
170 MultiPoly result(Rational(0), variables);
171 for (const auto& operand : addition->operands()) {
172 auto converted = node_to_multivariate_polynomial(
173 operand, variables, context);
174 if (!converted) { return converted; }
175 auto step = context.consume_steps(
176 static_cast<std::size_t>(result.num_terms() +
177 converted.value().num_terms()) + 1,
178 kOperation);
179 if (!step) { return Result<MultiPoly>::failure(step.error()); }
180 result = result + converted.value();
181 if (static_cast<std::size_t>(result.num_terms()) >
182 context.limits().max_expansion_terms) {
185 "polynomial expansion term budget exhausted", kOperation);
186 }
187 }
188 return Result<MultiPoly>::success(std::move(result));
189}
190
192 const std::shared_ptr<const PowerNode>& power,
193 const std::vector<std::string>& variables, ComputationContext& context) {
194 auto exponent = polynomial_exponent(power->exponent());
195 if (!exponent) { return Result<MultiPoly>::failure(exponent.error()); }
197 power->base(), variables, context);
198 if (!base) { return base; }
199
200 MultiPoly result(Rational(1), variables);
201 MultiPoly factor = std::move(base.value());
202 std::size_t remaining = exponent.value();
203 while (remaining != 0) {
204 if ((remaining & 1U) != 0) {
205 auto multiplied = multiply_checked(result, factor, context);
206 if (!multiplied) { return multiplied; }
207 result = std::move(multiplied.value());
208 }
209 remaining >>= 1U;
210 if (remaining != 0) {
211 auto squared = multiply_checked(factor, factor, context);
212 if (!squared) { return squared; }
213 factor = std::move(squared.value());
214 }
215 }
216 return Result<MultiPoly>::success(std::move(result));
217}
218
220 const Monomial& monomial, const Rational& coefficient,
221 const MultiPoly& polynomial, detail::RewriteBudget& budget) {
222 auto step = budget.context().consume_steps(1, kOperation);
223 if (!step) { return Result<std::shared_ptr<SymbolicExpr>>::failure(step.error()); }
224
225 auto factors = detail::multipoly_term_factors(
226 monomial, coefficient, polynomial.variables());
227 std::size_t nodes = 0;
228 for (const auto& factor : factors) {
229 nodes = budget.append_size(nodes, budget.measure(detail::node(factor)));
230 }
231 auto result = detail::multiply_expression_factors(factors);
232 budget.measure(detail::node(result));
233 return Result<std::shared_ptr<SymbolicExpr>>::success(std::move(result));
234}
235
237 const std::shared_ptr<const SymbolicNode>& node,
238 const std::vector<std::string>& variables,
240 auto entered = context.enter_recursion(kOperation);
241 if (!entered) { return Result<MultiPoly>::failure(entered.error()); }
242 RecursionFrame frame(context);
243
244 if (auto number = std::dynamic_pointer_cast<const NumberNode>(node)) {
246 MultiPoly(exact_number_as_rational(*number), variables));
247 }
248 if (auto variable = std::dynamic_pointer_cast<const VariableNode>(node)) {
249 Monomial monomial(variables.size(), 0);
250 auto position = std::find(variables.begin(), variables.end(),
251 variable->name());
252 if (position == variables.end()) {
255 "polynomial variable collection is inconsistent", kOperation);
256 }
257 monomial[static_cast<std::size_t>(position - variables.begin())] = 1;
258 return Result<MultiPoly>::success(MultiPoly(
259 {{std::move(monomial), Rational(1)}}, variables));
260 }
261 if (auto addition = std::dynamic_pointer_cast<const AddNode>(node)) {
262 return sum_to_polynomial(addition, variables, context);
263 }
264 if (auto product = std::dynamic_pointer_cast<const MultiplyNode>(node)) {
265 MultiPoly result(Rational(1), variables);
266 for (const auto& operand : product->operands()) {
267 auto converted = node_to_multivariate_polynomial(
268 operand, variables, context);
269 if (!converted) { return converted; }
270 auto multiplied = multiply_checked(result, converted.value(), context);
271 if (!multiplied) { return multiplied; }
272 result = std::move(multiplied.value());
273 }
274 return Result<MultiPoly>::success(std::move(result));
275 }
276 if (auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
277 return power_to_polynomial(power, variables, context);
278 }
281 "expression is outside the exact rational polynomial domain",
282 kOperation);
283}
284
285MultiPoly make_monic(const MultiPoly& polynomial) {
286 if (polynomial.is_zero()) { return polynomial; }
287 const Rational leading = polynomial.terms().front().second;
288 return polynomial * (Rational(1) / leading);
289}
290
292 const MultiPoly& polynomial,
293 detail::RewriteBudget& budget) {
294 if (polynomial.is_zero()) {
295 budget.require_nodes(1);
297 SymbolicExpr::number(0));
298 }
299
300 std::vector<std::shared_ptr<SymbolicExpr>> terms;
301 terms.reserve(polynomial.terms().size());
302 std::size_t nodes = 0;
303 for (const auto& [monomial, coefficient] : polynomial.terms()) {
305 monomial, coefficient, polynomial, budget);
306 if (!term) { return term; }
307 nodes = budget.append_size(nodes, budget.measure(detail::node(term.value())));
308 terms.push_back(std::move(term.value()));
309 }
310
311 auto expression = terms.front();
312 for (std::size_t index = 1; index < terms.size(); ++index) {
313 expression = SymbolicExpr::add(expression, terms[index]);
314 budget.measure(detail::node(expression));
315 }
316 return Result<std::shared_ptr<SymbolicExpr>>::success(std::move(expression));
317}
318Result<void> verify_gcd_candidate(const MultiPoly& lhs, const MultiPoly& rhs,
319 const MultiPoly& gcd) {
320 if (gcd.is_zero()) { return Result<void>::success(); }
321 MultiPoly lhs_quotient;
322 MultiPoly rhs_quotient;
323 try {
324 lhs_quotient = lhs.exact_div(gcd);
325 rhs_quotient = rhs.exact_div(gcd);
326 } catch (const std::bad_alloc&) {
329 "symbolic GCD verification allocation failed", kOperation);
330 } catch (const std::runtime_error&) {
333 "GCD candidate failed exact divisibility verification",
334 kOperation);
335 } catch (const std::exception& error) {
338 }
339 MultiPoly residual = make_monic(multivariate_gcd(
340 lhs_quotient, rhs_quotient));
341 if (!residual.is_constant()) {
344 "GCD candidate is not maximal", kOperation);
345 }
346 return Result<void>::success();
347}
348
349} // namespace
350
352 const SymbolicExpr& lhs,
353 const SymbolicExpr& rhs,
355 try {
356 detail::RewriteBudget budget(context, context.limits().max_recursion_depth,
357 context.limits().max_ast_nodes, kOperation);
358 budget.measure(detail::node(lhs));
359 budget.measure(detail::node(rhs));
360 std::set<std::string> variable_set;
361 auto lhs_variables = collect_polynomial_variables(
362 detail::node(lhs), variable_set, context);
363 if (!lhs_variables) { return SymbolicGcdResult::failure(lhs_variables.error()); }
364 auto rhs_variables = collect_polynomial_variables(
365 detail::node(rhs), variable_set, context);
366 if (!rhs_variables) { return SymbolicGcdResult::failure(rhs_variables.error()); }
367 std::vector<std::string> variables(variable_set.begin(), variable_set.end());
368
369 auto lhs_polynomial = node_to_multivariate_polynomial(
370 detail::node(lhs), variables, context);
371 if (!lhs_polynomial) { return SymbolicGcdResult::failure(lhs_polynomial.error()); }
372 auto rhs_polynomial = node_to_multivariate_polynomial(
373 detail::node(rhs), variables, context);
374 if (!rhs_polynomial) { return SymbolicGcdResult::failure(rhs_polynomial.error()); }
375
376 auto step = context.consume_steps(
377 static_cast<std::size_t>(lhs_polynomial.value().num_terms() +
378 rhs_polynomial.value().num_terms()) + 1,
379 kOperation);
380 if (!step) { return SymbolicGcdResult::failure(step.error()); }
381
382 MultiPoly gcd = make_monic(multivariate_gcd(
383 lhs_polynomial.value(), rhs_polynomial.value()));
384 auto verified = verify_gcd_candidate(
385 lhs_polynomial.value(), rhs_polynomial.value(), gcd);
386 if (!verified) { return SymbolicGcdResult::failure(verified.error()); }
387 return multivariate_polynomial_to_expression(gcd, budget);
388 } catch (const CasError& error) {
389 return SymbolicGcdResult::failure(error);
390 } catch (const std::length_error& error) {
391 return SymbolicGcdResult::failure(CasErrc::ResourceLimit, error.what(), kOperation);
392 } catch (const std::bad_alloc&) {
394 CasErrc::ResourceLimit, "symbolic GCD allocation failed", kOperation);
395 } catch (const std::exception& error) {
397 CasErrc::InternalInvariant, error.what(), kOperation);
398 }
399}
400
402 const SymbolicExpr& expression,
404 try {
405 detail::RewriteBudget budget(context, context.limits().max_recursion_depth,
406 context.limits().max_ast_nodes, kOperation);
407 budget.measure(detail::node(expression));
408 std::set<std::string> variable_set;
409 auto variables_result = collect_polynomial_variables(
410 detail::node(expression), variable_set, context);
411 if (!variables_result) {
412 return Result<Rational>::failure(variables_result.error());
413 }
414 std::vector<std::string> variables(
415 variable_set.begin(), variable_set.end());
416 auto polynomial = node_to_multivariate_polynomial(
417 detail::node(expression), variables, context);
418 if (!polynomial) {
419 return Result<Rational>::failure(polynomial.error());
420 }
421 return Result<Rational>::success(polynomial.value().numeric_content());
422 } catch (const CasError& error) {
423 return Result<Rational>::failure(error);
424 } catch (const std::length_error& error) {
425 return Result<Rational>::failure(CasErrc::ResourceLimit, error.what(), kOperation);
426 } catch (const std::bad_alloc&) {
428 CasErrc::ResourceLimit, "polynomial content allocation failed",
429 kOperation);
430 } catch (const std::exception& error) {
432 CasErrc::InternalInvariant, error.what(), kOperation);
433 }
434}
435
436} // namespace LMCAS
Result< void > consume_steps(std::size_t amount=1, const std::string &operation={})
static Result failure(CasErrc code, std::string message, std::string operation={})
static Result success(T value)
RecursionFrame & operator=(const RecursionFrame &)=delete
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)
多元多项式因式分解器公共 API。
Result< MultiPoly > node_to_multivariate_polynomial(const std::shared_ptr< const SymbolicNode > &node, const std::vector< std::string > &variables, ComputationContext &context)
Result< MultiPoly > sum_to_polynomial(const std::shared_ptr< const AddNode > &addition, const std::vector< std::string > &variables, ComputationContext &context)
Result< std::shared_ptr< SymbolicExpr > > multivariate_polynomial_to_expression(const MultiPoly &polynomial, detail::RewriteBudget &budget)
Result< void > verify_gcd_candidate(const MultiPoly &lhs, const MultiPoly &rhs, const MultiPoly &gcd)
Result< std::size_t > polynomial_exponent(const std::shared_ptr< const SymbolicNode > &node)
Result< std::shared_ptr< SymbolicExpr > > polynomial_term_to_expression(const Monomial &monomial, const Rational &coefficient, const MultiPoly &polynomial, detail::RewriteBudget &budget)
Result< MultiPoly > multiply_checked(const MultiPoly &lhs, const MultiPoly &rhs, ComputationContext &context)
MultiPoly make_monic(const MultiPoly &polynomial)
Result< void > collect_polynomial_operands(const std::vector< std::shared_ptr< const SymbolicNode > > &operands, std::set< std::string > &variables, ComputationContext &context)
Result< void > collect_polynomial_variables(const std::shared_ptr< const SymbolicNode > &node, std::set< std::string > &variables, ComputationContext &context)
Result< MultiPoly > power_to_polynomial(const std::shared_ptr< const PowerNode > &power, const std::vector< std::string > &variables, ComputationContext &context)
Result< void > check_term_budget(std::size_t lhs_terms, std::size_t rhs_terms, ComputationContext &context)
const SymbolicNodePtr & node(const ::LMCAS::SymbolicExpr &expression) noexcept
std::vector< std::shared_ptr< SymbolicExpr > > multipoly_term_factors(const Monomial &monomial, const Rational &coefficient, const std::vector< std::string > &variables)
std::shared_ptr< SymbolicExpr > multiply_expression_factors(const std::vector< std::shared_ptr< SymbolicExpr > > &factors)
const std::string & variable
std::vector< int > Monomial
单项式类型,以整数向量表示各变量的指数
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
SymbolicGcdResult symbolic_polynomial_gcd(const SymbolicExpr &lhs, const SymbolicExpr &rhs, ComputationContext &context)
ExprResult integer(Integer)=delete
Result< std::shared_ptr< SymbolicExpr > > SymbolicGcdResult
const ExprPtr & exponent
const std::string const ExprPtr & value
const ExprPtr & rhs
MultiPoly multivariate_gcd(const MultiPoly &a, const MultiPoly &b)
Rational exact_number_as_rational(const NumberNode &node)
Result< Rational > symbolic_polynomial_content(const SymbolicExpr &expression, ComputationContext &context)
ExprResult rational(const Rational &value)
多项式转换与精确符号多项式代数。
内部 AST 定义。