LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
calculus_asymptotes.cpp
浏览该文件的文档.
1#include <stdexcept>
3#include "limit_result.hpp"
4#include "poly_utils.hpp"
5#include "root_of_utils.hpp"
9
10namespace LMCAS {
11
12using namespace calculus_utils_detail;
13
14static std::shared_ptr<const SymbolicNode> denominator_factor(
15 const std::shared_ptr<const SymbolicNode>& node)
16{
17 auto power = std::dynamic_pointer_cast<const PowerNode>(node);
18 if (!power) {
19 return nullptr;
20 }
21 auto number = std::dynamic_pointer_cast<const NumberNode>(power->exponent());
22 if (!number) {
23 return nullptr;
24 }
25 const Rational exponent = std::holds_alternative<BigInt>(number->value())
26 ? Rational(std::get<BigInt>(number->value()))
27 : std::get<Rational>(number->value());
28 if (!(exponent < Rational(0))) {
29 return nullptr;
30 }
31 if (exponent == Rational(-1)) {
32 return power->base();
33 }
35 return LMCAS::detail::make_node<PowerNode>(power->base(), positive);
36}
37
38static std::shared_ptr<SymbolicExpr> calculus_utils_extract_denominator(
39 const std::shared_ptr<SymbolicExpr>& expr)
40{
41 if (!expr || !LMCAS::detail::node(expr)) {
42 return nullptr;
43 }
44 auto factor = denominator_factor(LMCAS::detail::node(expr));
45 if (factor) {
47 }
48 auto multiply = std::dynamic_pointer_cast<const MultiplyNode>(LMCAS::detail::node(expr));
49 if (!multiply) {
50 return nullptr;
51 }
52 std::vector<std::shared_ptr<const SymbolicNode>> factors;
53 for (const auto& operand : multiply->operands()) {
54 auto denominator = denominator_factor(operand);
55 if (denominator) factors.push_back(std::move(denominator));
56 }
57 if (factors.empty()) {
58 return nullptr;
59 }
60 if (factors.size() == 1) {
61 return LMCAS::detail::make_expression_ptr(factors[0]);
62 }
65}
66
67static std::shared_ptr<SymbolicExpr> calculus_utils_product(
68 const std::vector<std::shared_ptr<const SymbolicNode>>& factors, ComputationContext& context)
69{
70
71 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
72 (context).limits().max_ast_nodes, "transform");
73 if (factors.empty()) {
74 return SymbolicExpr::number(1);
75 }
76 if (factors.size() == 1) {
77 return LMCAS::detail::make_expression_ptr(factors[0]);
78 }
80}
81
82static bool calculus_utils_is_minus_one(const std::shared_ptr<const NumberNode>& number)
83{
84 if (!number) {
85 return false;
86 }
87 if (std::holds_alternative<BigInt>(number->value())) {
88 return std::get<BigInt>(number->value()) == BigInt(-1);
89 }
90 return std::get<Rational>(number->value()) == Rational(-1);
91}
92
94 const std::shared_ptr<SymbolicExpr>& expr,
95 std::shared_ptr<SymbolicExpr>& numerator,
96 std::shared_ptr<SymbolicExpr>& denominator, ComputationContext& context)
97{
98 if (!expr || !LMCAS::detail::node(expr)) {
99 return false;
100 }
101
102 std::vector<std::shared_ptr<const SymbolicNode>> num_factors;
103 std::vector<std::shared_ptr<const SymbolicNode>> den_factors;
104
105 auto collect_factor = [&](const std::shared_ptr<const SymbolicNode>& factor) {
106 if (auto power = std::dynamic_pointer_cast<const PowerNode>(factor)) {
107 auto exponent = std::dynamic_pointer_cast<const NumberNode>(power->exponent());
109 den_factors.push_back(power->base());
110 return;
111 }
112 }
113 num_factors.push_back(factor);
114 };
115
116 if (auto multiply = std::dynamic_pointer_cast<const MultiplyNode>(LMCAS::detail::node(expr))) {
117 for (const auto& factor : multiply->operands()) collect_factor(factor);
118 } else {
119 collect_factor(LMCAS::detail::node(expr));
120 }
121
122 if (den_factors.empty()) {
123 return false;
124 }
125 numerator = calculus_utils_product(num_factors, context);
127 return numerator && denominator;
128}
129
131 const std::shared_ptr<SymbolicExpr>& f,
132 const std::string& var,
133 std::shared_ptr<SymbolicExpr>& slope,
134 std::shared_ptr<SymbolicExpr>& intercept, ComputationContext& context)
135{
136
137 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
138 (context).limits().max_ast_nodes, "transform");
139 std::shared_ptr<SymbolicExpr> numerator;
140 std::shared_ptr<SymbolicExpr> denominator;
142 return false;
143 }
144
145 try {
146 auto num_poly = symbolic_to_poly<Rational>(detail::make_expression_ptr(detail::expand_node(detail::node(numerator), transform_budget)), var);
147 auto den_poly = symbolic_to_poly<Rational>(detail::make_expression_ptr(detail::expand_node(detail::node(denominator), transform_budget)), var);
148 if (!num_poly || !den_poly || den_poly.value().is_zero()) {
149 return false;
150 }
151 auto [quotient, remainder] = num_poly.value().div_mod(den_poly.value());
152 (void)remainder;
153 if (quotient.degree() != 1 || quotient.coeffs.size() < 2) {
154 return false;
155 }
156 intercept = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::number(quotient.coeffs[0])), transform_budget));
157 slope = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::number(quotient.coeffs[1])), transform_budget));
158 return !slope->is_zero();
159 } catch (const std::invalid_argument&) {
160 return false;
161 } catch (const std::out_of_range&) {
162 return false;
163 }
164}
166 const std::shared_ptr<SymbolicExpr>& denominator,
167 const std::string& var, ComputationContext& context,
168 const std::string& operation)
169{
170 auto solved = solve_equation(denominator, var, context, SolveOptions{});
171 if (!solved) {
172 return SymbolicExprVectorResult::failure(solved.error());
173 }
174 const auto& zero_set = solved.value();
175 const auto* finite = std::get_if<FiniteSolutions>(&zero_set);
176 if (!std::holds_alternative<EmptySolutions>(zero_set) && !finite) {
179 "asymptote denominator zeros are outside the finite exact support domain",
180 operation);
181 }
182 std::vector<std::shared_ptr<SymbolicExpr>> zeros;
183 if (!finite) {
184 return SymbolicExprVectorResult::success(std::move(zeros));
185 }
186 zeros.reserve(finite->values.size());
187 for (const auto& solution : finite->values) {
188 if (!solution.value || !LMCAS::detail::node(solution.value)) {
191 "checked solver returned a null vertical-asymptote candidate", operation);
192 }
193 auto candidate = rootof_simplify(solution.value);
194 if (!candidate || !LMCAS::detail::node(candidate)) {
197 "checked solver returned a malformed vertical-asymptote candidate", operation);
198 }
202 "vertical-asymptote candidate could not be reduced to an exact explicit point",
203 operation);
204 }
205 zeros.push_back(candidate);
206 }
207 return SymbolicExprVectorResult::success(std::move(zeros));
208}
209
211 const std::shared_ptr<SymbolicExpr>& f, const std::string& var,
212 const std::shared_ptr<SymbolicExpr>& point, ComputationContext& context)
213{
215 if (!right && right.error().code != CasErrc::Inconclusive) {
216 return Result<bool>::failure(right.error());
217 }
219 if (!left && left.error().code != CasErrc::Inconclusive) {
220 return Result<bool>::failure(left.error());
221 }
222 auto infinite = [](const LimitResult& value) {
223 return value && (std::holds_alternative<PositiveInfinityLimit>(value.value().value) ||
224 std::holds_alternative<NegativeInfinityLimit>(value.value().value));
225 };
226 if (infinite(right) || infinite(left)) { return true; }
227 if (!right) { return Result<bool>::failure(right.error()); }
228 if (!left) { return Result<bool>::failure(left.error()); }
229 return false;
230}
231
233 const std::shared_ptr<SymbolicExpr>& f, const std::string& var,
235 const std::string& operation)
236{
238 if (!denominator) {
239 return Result<void>::success();
240 }
241 auto zeros = asymptote_candidates(denominator, var, context, operation);
242 if (!zeros) {
243 return Result<void>::failure(zeros.error());
244 }
245 for (const auto& point : zeros.value()) {
247 continue;
248 }
249 auto vertical = is_vertical_asymptote(f, var, point, context);
250 if (!vertical) {
251 return Result<void>::failure(vertical.error());
252 }
253 if (vertical.value()) result.vertical.push_back(point);
254 }
255 return Result<void>::success();
256}
257
259 const AsymptoteResult& result, const std::shared_ptr<SymbolicExpr>& slope,
260 const std::shared_ptr<SymbolicExpr>& intercept)
261{
262 for (const auto& entry : result.oblique) {
263 if (calculus_utils_expr_equal(entry.first, slope) &&
264 calculus_utils_expr_equal(entry.second, intercept)) {
265 return true;
266 }
267 }
268 return false;
269}
270
272 const std::shared_ptr<SymbolicExpr>& f, const std::string& var,
273 const std::shared_ptr<SymbolicExpr>& x_expr,
274 const std::shared_ptr<SymbolicExpr>& infinity, ComputationContext& context,
275 AsymptoteResult& result)
276try {
277 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
278 (context).limits().max_ast_nodes, "transform");
279 auto quotient = SymbolicExpr::multiply(f,
280 SymbolicExpr::power(x_expr, SymbolicExpr::number(-1)));
281 auto slope_result = limit_expression_checked(
282 quotient, var, infinity, LimitDirection::Both, context);
283 if (!slope_result) {
284 return Result<void>::failure(slope_result.error());
285 }
286 auto slope = std::move(slope_result.value());
287 if (slope) slope = detail::make_expression_ptr(detail::normalize_node(detail::node(slope), transform_budget));
288 if (!slope || calculus_utils_is_infinity(slope) || slope->is_zero()) {
289 return Result<void>::success();
290 }
291 auto linear = SymbolicExpr::multiply(slope, x_expr);
292 auto remainder = SymbolicExpr::add(f,
293 SymbolicExpr::multiply(SymbolicExpr::number(-1), linear));
294 auto intercept_result = limit_expression_checked(
295 remainder, var, infinity, LimitDirection::Both, context);
296 if (!intercept_result) {
297 return Result<void>::failure(intercept_result.error());
298 }
299 auto intercept = std::move(intercept_result.value());
300 if (intercept) intercept = detail::make_expression_ptr(detail::normalize_node(detail::node(intercept), transform_budget));
301 if (intercept && !calculus_utils_is_infinity(intercept)) {
302 if (!has_oblique_asymptote(result, slope, intercept)) {
303 result.oblique.emplace_back(slope, intercept);
304 }
305 }
306 return Result<void>::success();
307} catch (const CasError& error) {
308 return Result<void>::failure(error);
309} catch (const std::bad_alloc&) {
310 return Result<void>::failure(CasErrc::ResourceLimit, "allocation failed", "collect_oblique_asymptote");
311} catch (const std::length_error& error) {
312 return Result<void>::failure(CasErrc::ResourceLimit, error.what(), "collect_oblique_asymptote");
313} catch (const std::exception& error) {
314 return Result<void>::failure(CasErrc::InternalInvariant, error.what(), "collect_oblique_asymptote");
315}
316
317static bool simplify_finite_limit(std::shared_ptr<SymbolicExpr>& value, ComputationContext& context)
318{
319
320 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
321 (context).limits().max_ast_nodes, "transform");
322 if (value) {
324 }
326}
327
329 const std::shared_ptr<SymbolicExpr>& f, const std::string& var,
330 const std::shared_ptr<SymbolicExpr>& pos_inf,
331 const std::shared_ptr<SymbolicExpr>& neg_inf,
333{
334 auto positive = limit_expression_checked(f, var, pos_inf, LimitDirection::Both, context);
335 if (!positive) {
336 return Result<void>::failure(positive.error());
337 }
338 auto negative = limit_expression_checked(f, var, neg_inf, LimitDirection::Both, context);
339 if (!negative) {
340 return Result<void>::failure(negative.error());
341 }
342 auto lim_pos = std::move(positive.value());
343 auto lim_neg = std::move(negative.value());
344 const bool horizontal_pos = simplify_finite_limit(lim_pos, context);
345 const bool horizontal_neg = simplify_finite_limit(lim_neg, context);
346 if (horizontal_pos) result.horizontal.push_back(lim_pos);
347 if (horizontal_neg) {
348 if (!horizontal_pos || !calculus_utils_expr_equal(lim_pos, lim_neg)) {
349 result.horizontal.push_back(lim_neg);
350 }
351 }
352 auto x_expr = SymbolicExpr::variable(var);
353 if (!horizontal_pos && !horizontal_neg) {
354 std::shared_ptr<SymbolicExpr> slope;
355 std::shared_ptr<SymbolicExpr> intercept;
356 if (calculus_utils_oblique_from_rational(f, var, slope, intercept, context)) {
357 result.oblique.emplace_back(slope, intercept);
358 return Result<void>::success();
359 }
360 }
361 if (!horizontal_pos) {
362 auto status = collect_oblique_asymptote(f, var, x_expr, pos_inf, context, result);
363 if (!status) {
364 return status;
365 }
366 }
367 if (!horizontal_neg) {
368 return collect_oblique_asymptote(f, var, x_expr, neg_inf, context, result);
369 }
370 return Result<void>::success();
371}
372
374 const std::shared_ptr<SymbolicExpr>& f, const std::string& var,
376{
377 try {
378 AsymptoteResult result;
379 const std::string operation = "asymptotes";
380 auto input = calculus_utils_validate_expr(f, var, context, operation);
381 if (!input) {
382 return AsymptoteAnalysisResult::failure(input.error());
383 }
384 auto pos_inf = SymbolicExpr::infinity(1);
385 auto neg_inf = SymbolicExpr::infinity(-1);
386 auto vertical = collect_vertical_asymptotes(f, var, context, result, operation);
387 if (!vertical) {
388 return AsymptoteAnalysisResult::failure(vertical.error());
389 }
390 auto infinite = collect_infinite_asymptotes(f, var, pos_inf, neg_inf, context, result);
391 if (!infinite) {
392 return AsymptoteAnalysisResult::failure(infinite.error());
393 }
394 return AsymptoteAnalysisResult::success(std::move(result));
395 } catch (const std::bad_alloc&) {
397 CasErrc::ResourceLimit, "asymptote analysis allocation failed", "asymptotes");
398 } catch (const std::exception& ex) {
400 CasErrc::InternalInvariant, ex.what(), "asymptotes");
401 }
402}
403
405 const std::shared_ptr<SymbolicExpr>& f, const std::string& var)
406{
408 return asymptotes_checked(f, var, context);
409}
410
411}
static Result failure(CasErrc code, std::string message, std::string operation={})
static Result success(std::vector< std::shared_ptr< SymbolicExpr > > value)
bool calculus_utils_is_infinity(const std::shared_ptr< SymbolicExpr > &expr)
判断表达式是否包含无穷大节点.
bool calculus_utils_expr_equal(const std::shared_ptr< SymbolicExpr > &a, const std::shared_ptr< SymbolicExpr > &b)
判断两个表达式是否结构相等.
Result< void > calculus_utils_validate_expr(const std::shared_ptr< SymbolicExpr > &expr, const std::string &var, ComputationContext &context, const std::string &operation)
SymbolicNodePtr expand_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
std::shared_ptr<::LMCAS::SymbolicExpr > make_expression_ptr(SymbolicNodePtr root)
const SymbolicNodePtr & node(const ::LMCAS::SymbolicExpr &expression) noexcept
bool contains_node_type(const SymbolicNodePtr &root)
std::shared_ptr< const Node > make_node(Args &&... args)
SymbolicNodePtr normalize_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
const std::string const LimitExprPtr & point
static Result< void > collect_oblique_asymptote(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, const std::shared_ptr< SymbolicExpr > &x_expr, const std::shared_ptr< SymbolicExpr > &infinity, ComputationContext &context, AsymptoteResult &result)
std::shared_ptr< SymbolicExpr > rootof_simplify(const std::shared_ptr< SymbolicExpr > &rootof_expr)
LimitResult limit_checked(const LimitExprPtr &expression, const std::string &variable, const LimitExprPtr &point, LimitDirection direction, ComputationContext &context, Domain domain)
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
AsymptoteAnalysisResult asymptotes_checked(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, ComputationContext &context)
static std::shared_ptr< SymbolicExpr > calculus_utils_product(const std::vector< std::shared_ptr< const SymbolicNode > > &factors, ComputationContext &context)
LMCAS::SolveResult solve_equation(const std::shared_ptr< SymbolicExpr > &expr, const std::string &var, ComputationContext &context, const SolveOptions &opts)
LimitExpressionResult limit_expression_checked(const LimitExprPtr &expression, const std::string &variable, const LimitExprPtr &point, LimitDirection direction, ComputationContext &context, Domain domain)
Result< std::vector< std::shared_ptr< SymbolicExpr > > > SymbolicExprVectorResult
static SymbolicExprVectorResult asymptote_candidates(const std::shared_ptr< SymbolicExpr > &denominator, const std::string &var, ComputationContext &context, const std::string &operation)
static bool simplify_finite_limit(std::shared_ptr< SymbolicExpr > &value, ComputationContext &context)
const ExprPtr & exponent
static Result< bool > is_vertical_asymptote(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, const std::shared_ptr< SymbolicExpr > &point, ComputationContext &context)
Result< Verified< LimitOutcome > > LimitResult
static Result< void > collect_vertical_asymptotes(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, ComputationContext &context, AsymptoteResult &result, const std::string &operation)
static Result< void > collect_infinite_asymptotes(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, const std::shared_ptr< SymbolicExpr > &pos_inf, const std::shared_ptr< SymbolicExpr > &neg_inf, ComputationContext &context, AsymptoteResult &result)
static bool calculus_utils_split_rational(const std::shared_ptr< SymbolicExpr > &expr, std::shared_ptr< SymbolicExpr > &numerator, std::shared_ptr< SymbolicExpr > &denominator, ComputationContext &context)
static bool calculus_utils_is_minus_one(const std::shared_ptr< const NumberNode > &number)
Result< AsymptoteResult > AsymptoteAnalysisResult
const ExprPtr & right
const std::string const ExprPtr & value
static bool calculus_utils_oblique_from_rational(const std::shared_ptr< SymbolicExpr > &f, const std::string &var, std::shared_ptr< SymbolicExpr > &slope, std::shared_ptr< SymbolicExpr > &intercept, ComputationContext &context)
static bool has_oblique_asymptote(const AsymptoteResult &result, const std::shared_ptr< SymbolicExpr > &slope, const std::shared_ptr< SymbolicExpr > &intercept)
const ExprPtr & denominator
static std::shared_ptr< SymbolicExpr > calculus_utils_extract_denominator(const std::shared_ptr< SymbolicExpr > &expr)
static std::shared_ptr< const SymbolicNode > denominator_factor(const std::shared_ptr< const SymbolicNode > &node)
方程求解选项,控制数值求解行为和结果形式.
多项式转换与精确符号多项式代数。
RootOf 恒等关系、求值及代数闭式化简。
方程求解策略调度器 solve_dispatch 及相关选项结构体.
根据左右极限与函数值,在给定上下文假设下证明实数域中的连续性分类。 跳跃与可去间断的比较需证明逐点不等,相等性需保证各值有定义;形式非恒等不足以证明二者。 参数值或有定义性未确定时返回 Inconclu...
std::vector< std::pair< std::shared_ptr< SymbolicExpr >, std::shared_ptr< SymbolicExpr > > > oblique
斜渐近线 (斜率, 截距)
std::vector< std::shared_ptr< SymbolicExpr > > vertical
垂直渐近线 x = a
std::vector< std::shared_ptr< SymbolicExpr > > horizontal
水平渐近线 y = L
内部 AST 定义。