LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
series_convergence.cpp
浏览该文件的文档.
1#include <stdexcept>
3#include "limit_result.hpp"
4#include "series_engine.hpp"
5#include "symbolic.hpp"
9#include <cmath>
10#include <limits>
11#include <memory>
12#include <optional>
13#include <string>
14#include <variant>
15
16namespace LMCAS {
17
18namespace detail::series_support {
19double series_number_value(const NumberNode& number) {
20 if (std::holds_alternative<BigInt>(number.value())) {
21 return std::get<BigInt>(number.value()).to_double();
22 }
23 return std::get<Rational>(number.value()).to_double();
24}
25
26bool series_is_number(const std::shared_ptr<SymbolicExpr>& expr) {
27 if (!expr || !LMCAS::detail::node(expr)) {
28 return false;
29 }
30 return expr->is_number();
31}
32
33double series_get_double(const std::shared_ptr<SymbolicExpr>& expr) {
34 if (!expr || !LMCAS::detail::node(expr)) {
35 return 0.0;
36 }
37 auto num = std::dynamic_pointer_cast<const NumberNode>(LMCAS::detail::node(expr));
38 if (!num) {
39 return 0.0;
40 }
41 return series_number_value(*num);
42}
43
44bool series_is_infinity(const std::shared_ptr<SymbolicExpr>& expr) {
45 if (!expr || !LMCAS::detail::node(expr)) {
46 return false;
47 }
48 auto func = std::dynamic_pointer_cast<const FunctionNode>(LMCAS::detail::node(expr));
49 if (func && func->type() == FunctionNode::FuncType::Infinity) {
50 return true;
51 }
52 if (series_is_number(expr)) {
53 return std::isinf(series_get_double(expr));
54 }
55 return false;
56}
57}
58
63
67
69 const std::shared_ptr<SymbolicExpr>&,
70 const std::string&,
72static bool series_index_variable(const std::shared_ptr<const VariableNode>& variable,
73 const std::string& index_var) {
74 return variable && !variable->is_constant() && variable->name() == index_var;
75}
76
77static std::optional<std::shared_ptr<SymbolicExpr>> coefficient_power_radius(
78 const PowerNode& power, const std::string& index_var, ComputationContext& context) {
79
80 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
81 (context).limits().max_ast_nodes, "transform");
82 auto exponent_variable = std::dynamic_pointer_cast<const VariableNode>(
83 power.exponent());
84 if (series_index_variable(exponent_variable, index_var) &&
85 !expression_depends_on_variable(power.base(), index_var)) {
89 std::vector<std::shared_ptr<const SymbolicNode>>{
90 power.base()}));
91 return detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::divide(SymbolicExpr::number(1), absolute)), transform_budget));
92 }
93 auto base_variable = std::dynamic_pointer_cast<const VariableNode>(
94 power.base());
95 if (series_index_variable(base_variable, index_var) &&
96 !expression_depends_on_variable(power.exponent(), index_var)) {
97 return SymbolicExpr::number(1);
98 }
99 return std::nullopt;
100}
101
103 const std::shared_ptr<SymbolicExpr>& general_coefficient,
104 const std::string& index_var,
106 constexpr const char* operation = "convergence_radius";
107 if (!general_coefficient || !LMCAS::detail::node(general_coefficient) ||
108 index_var.empty()) {
111 "convergence radius requires a coefficient term and index variable",
112 operation);
113 }
114 auto step = context.consume_steps(2, operation);
115 if (!step) {
116 return ExpressionResult::failure(step.error());
117 }
118 auto node = LMCAS::detail::node(general_coefficient);
119 if (auto number = std::dynamic_pointer_cast<const NumberNode>(node)) {
121 number->is_zero() ? SymbolicExpr::infinity()
122 : SymbolicExpr::number(1));
123 }
124 if (auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
125 auto radius = coefficient_power_radius(*power, index_var, context);
126 if (radius) {
127 return ExpressionResult::success(std::move(*radius));
128 }
129 }
132 "coefficient-term limit is outside the supported ratio/root-test domain",
133 operation);
134}
135
137 const std::shared_ptr<SymbolicExpr>& general_coefficient,
138 const std::string& index_var) {
141 general_coefficient, index_var, context);
142}
143
145 const std::vector<std::shared_ptr<SymbolicExpr>>& coefficients,
146 const std::string& var,
148{
149 const std::string operation = "convergence_radius";
150 auto var_check = validate_series_variable(var, context, operation);
151 if (!var_check) {
152 return ExpressionResult::failure(var_check.error());
153 }
154 if (coefficients.empty()) {
156 "coefficient list cannot be empty",
157 operation);
158 }
159 auto coeff_check = validate_power_series_coefficients(
160 coefficients, operation, "polynomial");
161 if (!coeff_check) {
162 return ExpressionResult::failure(coeff_check.error());
163 }
164 auto budget = context.consume_steps(coefficients.size() + 1, operation);
165 if (!budget) {
166 return ExpressionResult::failure(budget.error());
167 }
168
169 for (size_t index = 0; index < coefficients.size(); ++index) {
171 LMCAS::detail::node(coefficients[index]), var)) {
174 "coefficient at index " + std::to_string(index) +
175 " depends on the series variable",
176 operation);
177 }
178 }
179
180 try {
181 /// 有限系数列表完整地表示一个多项式,其收敛半径恒为无穷.
182 /// 无限级数必须使用通项重载,有限前缀不能证明其尾项行为.
183 return ExpressionResult::success(SymbolicExpr::infinity());
184 } catch (const std::bad_alloc&) {
187 "allocation failed while constructing convergence radius",
188 operation);
189 } catch (const std::exception& ex) {
191 CasErrc::InternalInvariant, ex.what(), operation);
192 }
193}
194
196 const std::vector<std::shared_ptr<SymbolicExpr>>& coefficients,
197 const std::string& var)
198{
200 return convergence_radius_checked(coefficients, var, context);
201}
202
203
205 const std::shared_ptr<SymbolicExpr>& general_term,
206 const std::string& index_var,
208{
209 const std::string operation = "convergence_test";
210 auto var_check = validate_series_variable(index_var, context, operation);
211 if (!var_check) {
212 return ConvergenceInfoResult::failure(var_check.error());
213 }
214 if (!general_term || !LMCAS::detail::node(general_term)) {
216 "general term cannot be null",
217 operation);
218 }
219 auto budget = context.consume_steps(32, operation);
220 if (!budget) {
221 return ConvergenceInfoResult::failure(budget.error());
222 }
223 try {
224 auto analyzed =
225 convergence_test_impl(general_term, index_var, context);
226 if (!analyzed) {
227 return analyzed;
228 }
229 auto info = std::move(analyzed.value());
230 if (info.result == ConvergenceResult::Inconclusive) {
233 "convergence test is outside the current supported domain",
234 operation);
235 }
236 return info;
237 } catch (const CasError& error) {
238 return ConvergenceInfoResult::failure(error);
239 } catch (const std::bad_alloc&) {
241 "allocation failed while testing convergence",
242 operation);
243 } catch (const std::exception& ex) {
245 ex.what(),
246 operation);
247 }
248}
249
251 const std::shared_ptr<SymbolicExpr>& general_term,
252 const std::string& index_var)
253{
255 return convergence_test_checked(general_term, index_var, context);
256}
257
258static std::optional<ConvergenceInfo> geometric_term_convergence(
259 const std::shared_ptr<const SymbolicNode>& node,
260 const std::string& index_var) {
261 auto power = std::dynamic_pointer_cast<const PowerNode>(node);
262 if (!power) {
263 return std::nullopt;
264 }
265 auto exponent = std::dynamic_pointer_cast<const VariableNode>(power->exponent());
266 auto base = std::dynamic_pointer_cast<const NumberNode>(power->base());
267 if (!exponent || exponent->is_constant() || exponent->name() != index_var || !base) {
268 return std::nullopt;
269 }
270
271 bool inside_unit_circle = false;
272 if (const auto* integer = std::get_if<BigInt>(&base->value())) {
273 inside_unit_circle = *integer > BigInt(-1) && *integer < BigInt(1);
274 } else if (const auto* rational = std::get_if<Rational>(&base->value())) {
275 inside_unit_circle = *rational > Rational(-1) && *rational < Rational(1);
276 }
277 return ConvergenceInfo{
278 inside_unit_circle ? ConvergenceResult::Convergent
280 "geometric"};
281}
282
283static std::optional<ConvergenceInfo> power_term_convergence(
284 const std::shared_ptr<const SymbolicNode>& node,
285 const std::string& index_var) {
286 auto power = std::dynamic_pointer_cast<const PowerNode>(node);
287 if (!power) {
288 return std::nullopt;
289 }
290 auto base_var = std::dynamic_pointer_cast<const VariableNode>(power->base());
291 auto exponent = std::dynamic_pointer_cast<const NumberNode>(power->exponent());
292 if (!base_var || base_var->is_constant() || base_var->name() != index_var || !exponent) {
293 return std::nullopt;
294 }
295
296 bool below_negative_one = false;
297 bool at_or_above_negative_one = false;
298 if (const auto* integer = std::get_if<BigInt>(&exponent->value())) {
299 below_negative_one = *integer < BigInt(-1);
300 at_or_above_negative_one = !below_negative_one;
301 } else if (const auto* rational = std::get_if<Rational>(&exponent->value())) {
302 below_negative_one = *rational < Rational(-1);
303 at_or_above_negative_one = !below_negative_one;
304 }
305 if (below_negative_one) {
306 return ConvergenceInfo{
308 }
309 if (at_or_above_negative_one) {
310 return ConvergenceInfo{
311 ConvergenceResult::Divergent, "p-series"};
312 }
313 return std::nullopt;
314}
315
317 const std::shared_ptr<SymbolicExpr>& lim, ComputationContext& context) {
318
319 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
320 (context).limits().max_ast_nodes, "transform");
321 if (!lim) {
323 }
324 auto ls = detail::make_expression_ptr(detail::normalize_node(detail::node(lim), transform_budget));
325 auto infinity = ls
326 ? std::dynamic_pointer_cast<const FunctionNode>(detail::node(ls))
327 : nullptr;
328 if (infinity && infinity->type() == FunctionNode::FuncType::Infinity) {
329 return {ConvergenceResult::Divergent, "ratio"};
330 }
331 auto number = ls
332 ? std::dynamic_pointer_cast<const NumberNode>(LMCAS::detail::node(ls))
333 : nullptr;
334 if (!number) {
336 }
337 if (const auto* integer = std::get_if<BigInt>(&number->value())) {
338 return {*integer < BigInt(1) ? ConvergenceResult::Convergent
341 }
342 if (const auto* rational = std::get_if<Rational>(&number->value())) {
343 return {*rational < Rational(1) ? ConvergenceResult::Convergent
344 : *rational > Rational(1) ? ConvergenceResult::Divergent
346 }
348}
349
351 const std::shared_ptr<SymbolicExpr>& general_term,
352 const std::string& index_var, ComputationContext& context) try {
353 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
354 (context).limits().max_ast_nodes, "transform");
355 auto n = SymbolicExpr::variable(index_var);
356 auto n1 = SymbolicExpr::add(n, SymbolicExpr::number(1));
357 auto inf = SymbolicExpr::infinity();
358 auto a_n1 = detail::make_expression_ptr(detail::substitute_node(detail::node(general_term), index_var, detail::node(n1), transform_budget));
359 if (a_n1) {
360 a_n1 = detail::make_expression_ptr(detail::normalize_node(detail::node(a_n1), transform_budget));
361 auto ratio = SymbolicExpr::divide(a_n1, general_term);
362 if (ratio) {
363 ratio = detail::make_expression_ptr(detail::normalize_node(detail::node(ratio), transform_budget));
365 FunctionNode::FuncType::Abs, std::vector<std::shared_ptr<const SymbolicNode>>{LMCAS::detail::node(ratio)}));
366 abs_r = detail::make_expression_ptr(detail::normalize_node(detail::node(abs_r), transform_budget));
367 auto limited = limit_expression_checked(
368 abs_r, index_var, inf, LimitDirection::Both, context);
369 if (!limited) {
370 return ConvergenceInfoResult::failure(limited.error());
371 }
372 auto lim = std::move(limited.value());
373 return classify_ratio_limit(lim, context);
374 }
375 }
377} catch (const CasError& error) {
378 return ConvergenceInfoResult::failure(error);
379} catch (const std::bad_alloc&) {
380 return ConvergenceInfoResult::failure(CasErrc::ResourceLimit, "allocation failed", "ratio_convergence_test");
381} catch (const std::length_error& error) {
382 return ConvergenceInfoResult::failure(CasErrc::ResourceLimit, error.what(), "ratio_convergence_test");
383} catch (const std::exception& error) {
384 return ConvergenceInfoResult::failure(CasErrc::InternalInvariant, error.what(), "ratio_convergence_test");
385}
386
388 const std::shared_ptr<SymbolicExpr>& general_term,
389 const std::string& index_var,
391 if (!general_term || !LMCAS::detail::node(general_term)) {
392 return ConvergenceInfo{
394 }
395 if (auto geometric = geometric_term_convergence(
396 detail::node(general_term), index_var)) {
397 return std::move(*geometric);
398 }
399 auto power_info = power_term_convergence(detail::node(general_term), index_var);
400 if (power_info) {
401 return std::move(*power_info);
402 }
403 /**
404 * @brief 对 Laurent 单项式 c*n^e 直接应用 p 级数判据。
405 * 倒数幂沿用线性规则,省去 Abs 比值极限的递归化简。
406 */
407 if (const auto laurent_power = supported_laurent_integer_power(
408 LMCAS::detail::node(general_term), index_var)) {
409 if (*laurent_power < -1) {
410 return ConvergenceInfo{
412 }
413 if (*laurent_power >= -1) {
414 return ConvergenceInfo{
415 ConvergenceResult::Divergent, "p-series"};
416 }
417 }
418 return ratio_convergence_test(general_term, index_var, context);
419}
420
421}
const std::variant< BigInt, Rational > & value() const noexcept
幂运算节点,表示 base^exponent。
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(std::shared_ptr< SymbolicExpr > value)
bool series_is_infinity(const std::shared_ptr< SymbolicExpr > &expression)
double series_number_value(const NumberNode &number)
Result< void > validate_power_series_coefficients(const std::vector< std::shared_ptr< SymbolicExpr > > &coefficients, const std::string &operation, const std::string &name)
bool series_is_number(const std::shared_ptr< SymbolicExpr > &expression)
double series_get_double(const std::shared_ptr< SymbolicExpr > &expression)
std::optional< int > supported_laurent_integer_power(const std::shared_ptr< const SymbolicNode > &node, const std::string &variable)
Result< void > validate_series_variable(const std::string &variable, 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 Node > make_node(Args &&... args)
SymbolicNodePtr normalize_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
SymbolicNodePtr substitute_node(const SymbolicNodePtr &node, const std::string &variable, const SymbolicNodePtr &replacement, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
std::shared_ptr< SymbolicExpr > num(int value)
Result< ConvergenceInfo > ConvergenceInfoResult
const std::string & variable
static ConvergenceInfoResult ratio_convergence_test(const std::shared_ptr< SymbolicExpr > &general_term, const std::string &index_var, ComputationContext &context)
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
ExprResult integer(Integer)=delete
static bool series_index_variable(const std::shared_ptr< const VariableNode > &variable, const std::string &index_var)
static std::optional< ConvergenceInfo > power_term_convergence(const std::shared_ptr< const SymbolicNode > &node, const std::string &index_var)
bool expression_depends_on_variable(const detail::SymbolicNodePtr &expression, const std::string &variable, detail::RewriteBudget *budget)
LimitExpressionResult limit_expression_checked(const LimitExprPtr &expression, const std::string &variable, const LimitExprPtr &point, LimitDirection direction, ComputationContext &context, Domain domain)
Result< std::shared_ptr< SymbolicExpr > > ExpressionResult
Result of an operation that produces a symbolic expression.
ConvergenceInfoResult convergence_test_checked(const std::shared_ptr< SymbolicExpr > &general_term, const std::string &index_var, ComputationContext &context)
const ExprPtr & exponent
Result< void > validate_power_series_coefficients(const std::vector< std::shared_ptr< SymbolicExpr > > &coefficients, const std::string &operation, const std::string &name)
static std::optional< ConvergenceInfo > geometric_term_convergence(const std::shared_ptr< const SymbolicNode > &node, const std::string &index_var)
static ConvergenceInfo classify_ratio_limit(const std::shared_ptr< SymbolicExpr > &lim, ComputationContext &context)
std::optional< int > supported_laurent_integer_power(const std::shared_ptr< const SymbolicNode > &node, const std::string &variable)
static std::optional< std::shared_ptr< SymbolicExpr > > coefficient_power_radius(const PowerNode &power, const std::string &index_var, ComputationContext &context)
ExpressionResult convergence_radius_checked(const std::shared_ptr< SymbolicExpr > &general_coefficient, const std::string &index_var, ComputationContext &context)
static ConvergenceInfoResult convergence_test_impl(const std::shared_ptr< SymbolicExpr > &, const std::string &, ComputationContext &)
ExprResult rational(const Rational &value)
Result< void > validate_series_variable(const std::string &variable, ComputationContext &context, const std::string &operation)
收敛性判定信息.
级数引擎模块:收敛性判定,幂级数运算,傅里叶级数,洛朗级数,渐近展开,符号求和与乘积.
符号表达式主类 SymbolicExpr 及其工厂方法,运算接口.
内部 AST 定义。