LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
ode_linear.cpp
浏览该文件的文档.
1#include <stdexcept>
3/**
4 * @file ode_linear.cpp
5 * @brief 常系数高阶与 Euler ODE 的解表达式构造和受检 API。
6 */
9#include "symbolic.hpp"
10#include "poly_utils.hpp"
14#include <algorithm>
15#include <cmath>
16#include <limits>
17#include <memory>
18#include <string>
19#include <utility>
20#include <vector>
21
22namespace LMCAS {
23
25 const std::vector<double>&,
26 const std::shared_ptr<SymbolicExpr>&,
27 const std::string&, const std::string&, ComputationContext& context);
29 const std::vector<double>&,
30 const std::shared_ptr<SymbolicExpr>&,
31 const std::string&, const std::string&, ComputationContext& context);
32
33
34using namespace ode_root_detail;
35
36/**
37 * @internal
38 * @brief 将 double 值转为"干净"的数值表达式.
39 *
40 * 仅在精确保持 double 值时使用小整数或简单分数表示.
41 */
43 return std::isfinite(value) &&
44 value >= static_cast<double>(std::numeric_limits<int>::min()) &&
45 value <= static_cast<double>(std::numeric_limits<int>::max());
46}
47
48
49static std::shared_ptr<SymbolicExpr> clean_number(double val) {
50 /// 只在转换可表示时提升为精确小整数,避免浮点到整数的越界转换。
51 double rounded = std::round(val);
52 if (is_representable_small_integer(rounded) && val == rounded) {
53 return SymbolicExpr::number(static_cast<int>(rounded));
54 }
55
56 /// 检查可由简单分数 p/q (q <= 12) 精确表示的 double 值。
57 for (int q = 2; q <= 12; ++q) {
58 double p = val * q;
59 double p_rounded = std::round(p);
60 if (is_representable_small_integer(p_rounded) &&
61 val == p_rounded / q) {
62 return SymbolicExpr::divide(
63 SymbolicExpr::number(static_cast<int>(p_rounded)),
64 SymbolicExpr::number(q));
65 }
66 }
67
68 return SymbolicExpr::number(val);
69}
70
71static bool has_nonzero_forcing(const std::shared_ptr<SymbolicExpr>& forcing) {
72 return forcing && LMCAS::detail::node(forcing) && !forcing->is_zero();
73}
74
75/**
76 * @internal
77 * @brief 根据特征根构造齐次通解.
78 *
79 * - 实根 r(重数 m):C_k * x^k * e^(rx),k = 0, ..., m-1
80 * - 复根 alpha+/-betai(重数 m):x^k * e^(alphax) * (C_a*cos(betax) + C_b*sin(betax))
81 */
82static std::shared_ptr<SymbolicExpr> build_homogeneous_solution(
83 const std::vector<CharRoot>& roots,
84 const std::string& x,
85 std::vector<std::string>& constants)
86{
87 auto x_var = SymbolicExpr::variable(x);
88 std::shared_ptr<SymbolicExpr> solution = nullptr;
89 int const_idx = 1;
90
91 for (const auto& root : roots) {
92 for (int k = 0; k < root.multiplicity; ++k) {
93 if (!root.is_complex) {
94 /// 实根: C_i * x^k * e^(r*x)
95 std::string c_name = "C" + std::to_string(const_idx++);
96 constants.push_back(c_name);
97 auto C = SymbolicExpr::variable(c_name);
98
99 std::shared_ptr<SymbolicExpr> term = C;
100
101 /// x^k 因子
102 if (k > 0) {
103 auto x_pow = SymbolicExpr::power(x_var, SymbolicExpr::number(k));
104 term = SymbolicExpr::multiply(term, x_pow);
105 }
106
107 /// e^(r*x) 因子。精确零才可省略;小的非零根仍改变解。
108 if (root.real_part != 0.0) {
109 auto r_expr = clean_number(root.real_part);
110 auto exp_arg = SymbolicExpr::multiply(r_expr, x_var);
111 auto exp_term = SymbolicExpr::exp(exp_arg);
112 term = SymbolicExpr::multiply(term, exp_term);
113 }
114
115 solution = solution ? SymbolicExpr::add(solution, term) : term;
116 } else {
117 /// 复根 alpha+/-betai: 产生两个基本解
118 /// C_a * x^k * e^(alphax) * cos(betax)
119 /// C_b * x^k * e^(alphax) * sin(betax)
120 std::string ca_name = "C" + std::to_string(const_idx++);
121 std::string cb_name = "C" + std::to_string(const_idx++);
122 constants.push_back(ca_name);
123 constants.push_back(cb_name);
124 auto Ca = SymbolicExpr::variable(ca_name);
125 auto Cb = SymbolicExpr::variable(cb_name);
126
127 auto beta_expr = clean_number(root.imag_part);
128 auto beta_x = SymbolicExpr::multiply(beta_expr, x_var);
129 auto cos_term = SymbolicExpr::cos(beta_x);
130 auto sin_term = SymbolicExpr::sin(beta_x);
131
132 /// e^(alphax) 因子。精确零才可省略。
133 std::shared_ptr<SymbolicExpr> exp_factor = nullptr;
134 if (root.real_part != 0.0) {
135 auto alpha_expr = clean_number(root.real_part);
136 auto exp_arg = SymbolicExpr::multiply(alpha_expr, x_var);
137 exp_factor = SymbolicExpr::exp(exp_arg);
138 }
139
140 /// x^k 因子
141 std::shared_ptr<SymbolicExpr> x_pow_factor = nullptr;
142 if (k > 0) {
143 x_pow_factor = SymbolicExpr::power(x_var, SymbolicExpr::number(k));
144 }
145
146 /// 构造 cos 项: Ca * x^k * e^(alphax) * cos(betax)
147 auto term_cos = Ca;
148 if (x_pow_factor) {
149 term_cos = SymbolicExpr::multiply(term_cos, x_pow_factor);
150 }
151 if (exp_factor) {
152 term_cos = SymbolicExpr::multiply(term_cos, exp_factor);
153 }
154 term_cos = SymbolicExpr::multiply(term_cos, cos_term);
155
156 /// 构造 sin 项: Cb * x^k * e^(alphax) * sin(betax)
157 auto term_sin = Cb;
158 if (x_pow_factor) {
159 term_sin = SymbolicExpr::multiply(term_sin, x_pow_factor);
160 }
161 if (exp_factor) {
162 term_sin = SymbolicExpr::multiply(term_sin, exp_factor);
163 }
164 term_sin = SymbolicExpr::multiply(term_sin, sin_term);
165
166 solution = solution ? SymbolicExpr::add(solution, term_cos) : term_cos;
167 solution = SymbolicExpr::add(solution, term_sin);
168 }
169 }
170 }
171
172 return solution;
173}
174
175static std::shared_ptr<SymbolicExpr> build_euler_solution(
176 const std::vector<CharRoot>& roots,
177 const std::string& x,
178 std::vector<std::string>& constants)
179{
180 auto x_var = SymbolicExpr::variable(x);
181 auto ln_x = SymbolicExpr::ln(x_var);
182 std::shared_ptr<SymbolicExpr> solution = nullptr;
183 int const_idx = 1;
184
185 for (const auto& root : roots) {
186 for (int k = 0; k < root.multiplicity; ++k) {
187 std::shared_ptr<SymbolicExpr> log_factor = nullptr;
188 if (k > 0) {
189 log_factor = SymbolicExpr::power(ln_x, SymbolicExpr::number(k));
190 }
191
192 if (!root.is_complex) {
193 std::string c_name = "C" + std::to_string(const_idx++);
194 constants.push_back(c_name);
195 auto term = SymbolicExpr::variable(c_name);
196
197 auto exponent = clean_number(root.real_part);
198 term = SymbolicExpr::multiply(
199 term,
200 SymbolicExpr::power(x_var, exponent));
201 if (log_factor) {
202 term = SymbolicExpr::multiply(term, log_factor);
203 }
204 solution = solution ? SymbolicExpr::add(solution, term) : term;
205 } else {
206 std::string ca_name = "C" + std::to_string(const_idx++);
207 std::string cb_name = "C" + std::to_string(const_idx++);
208 constants.push_back(ca_name);
209 constants.push_back(cb_name);
210
211 auto alpha_expr = clean_number(root.real_part);
212 auto beta_expr = clean_number(root.imag_part);
213 auto beta_ln_x = SymbolicExpr::multiply(beta_expr, ln_x);
214 auto trig = SymbolicExpr::add(
215 SymbolicExpr::multiply(SymbolicExpr::variable(ca_name),
216 SymbolicExpr::cos(beta_ln_x)),
217 SymbolicExpr::multiply(SymbolicExpr::variable(cb_name),
218 SymbolicExpr::sin(beta_ln_x)));
219 auto term = SymbolicExpr::multiply(
220 SymbolicExpr::power(x_var, alpha_expr),
221 trig);
222 if (log_factor) {
223 term = SymbolicExpr::multiply(term, log_factor);
224 }
225 solution = solution ? SymbolicExpr::add(solution, term) : term;
226 }
227 }
228 }
229
230 return solution;
231}
232
234 const std::vector<double>& coeffs,
235 const std::shared_ptr<SymbolicExpr>& forcing,
236 const std::string& x,
237 const std::string& y,
239try {
240 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
241 (context).limits().max_ast_nodes, "transform");
242 const std::string operation = "solve_higher_order_ode";
243 auto variables = validate_ode_variables(x, y, context, operation);
244 if (!variables) {
245 return ODESolutionResult::failure(variables.error());
246 }
247
248 auto coeff_check = validate_numeric_ode_coefficients(coeffs, 2, 7, operation);
249 if (!coeff_check) {
250 return ODESolutionResult::failure(coeff_check.error());
251 }
252 for (double coefficient : coeffs) {
253 if (!std::isfinite(coefficient / coeffs.front())) {
256 "normalizing the characteristic polynomial exceeds the finite numeric domain",
257 operation);
258 }
259 }
260
261 auto budget = context.consume_steps(coeffs.size() * 20 + 20, operation);
262 if (!budget) {
263 return ODESolutionResult::failure(budget.error());
264 }
265
266 const bool nonzero_forcing = has_nonzero_forcing(forcing);
267 if (nonzero_forcing &&
268 (!forcing->is_number() || coeffs.back() == 0.0)) {
271 "non-homogeneous constant-coefficient ODE currently requires a constant forcing and nonzero y coefficient",
272 operation);
273 }
274
275 try {
276 auto solution_result = solve_higher_order_ode_impl(
277 coeffs, nullptr, x, y, context);
278 if (!solution_result) {
279 return ODESolutionResult::failure(solution_result.error());
280 }
281 auto solution = std::move(solution_result.value());
282 if (nonzero_forcing && solution.general_solution) {
283 auto particular = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::divide(
284 forcing, SymbolicExpr::number(coeffs.back()))), transform_budget));
285 solution.general_solution = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::add(
286 solution.general_solution, particular)), transform_budget));
287 }
288 return wrap_ode_solution(
289 std::move(solution),
291 operation);
292 } catch (const std::bad_alloc&) {
295 "allocation failed while solving higher-order ODE",
296 operation);
297 } catch (const std::length_error& error) { return ODESolutionResult::failure(CasErrc::ResourceLimit, error.what(), "solve_higher_order_ode"); } catch (const std::exception& ex) {
300 ex.what(),
301 operation);
302 }
303} catch (const CasError& error) {
304 return ODESolutionResult::failure(error);
305} catch (const std::bad_alloc&) {
306 return ODESolutionResult::failure(CasErrc::ResourceLimit, "allocation failed", "solve_higher_order_ode");
307} catch (const std::length_error& error) {
308 return ODESolutionResult::failure(CasErrc::ResourceLimit, error.what(), "solve_higher_order_ode");
309} catch (const std::exception& error) {
310 return ODESolutionResult::failure(CasErrc::InternalInvariant, error.what(), "solve_higher_order_ode");
311}
312
314 const std::vector<double>& coeffs,
315 const std::shared_ptr<SymbolicExpr>& forcing,
316 const std::string& x,
317 const std::string& y)
318{
320 return solve_higher_order_ode_checked(coeffs, forcing, x, y, context);
321}
322
324 const std::vector<double>& coeffs,
325 const std::shared_ptr<SymbolicExpr>& forcing,
326 const std::string& x,
327 const std::string&, ComputationContext& context)
328try {
329
330 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
331 (context).limits().max_ast_nodes, "transform");
332 const std::string operation = "solve_higher_order_ode";
333 ODESolution result;
335
336 if (coeffs.size() < 2 || has_nonzero_forcing(forcing)) {
339 "higher-order ODE is outside the implemented support domain",
340 operation);
341 }
342
343 auto roots = find_characteristic_roots(coeffs, operation);
344 if (!roots) {
346 }
347 result.general_solution =
349 if (result.general_solution) {
351 }
352 return Result<ODESolution>::success(std::move(result));
353} catch (const CasError& error) {
354 return Result<ODESolution>::failure(error);
355} catch (const std::bad_alloc&) {
356 return Result<ODESolution>::failure(CasErrc::ResourceLimit, "allocation failed", "solve_higher_order_ode");
357} catch (const std::length_error& error) {
358 return Result<ODESolution>::failure(CasErrc::ResourceLimit, error.what(), "solve_higher_order_ode");
359} catch (const std::exception& error) {
360 return Result<ODESolution>::failure(CasErrc::InternalInvariant, error.what(), "solve_higher_order_ode");
361}
362
364 const std::shared_ptr<SymbolicExpr>& forcing,
365 const std::vector<double>& coefficients) {
366 return !forcing->is_number() || coefficients.back() == 0.0;
367}
368
370 const std::vector<double>& euler_coeffs,
371 const std::shared_ptr<SymbolicExpr>& forcing,
372 const std::string& x,
373 const std::string& y,
375try {
376 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
377 (context).limits().max_ast_nodes, "transform");
378 const std::string operation = "solve_euler_ode";
379 auto variables = validate_ode_variables(x, y, context, operation);
380 if (!variables) {
381 return ODESolutionResult::failure(variables.error());
382 }
383
384 auto coeff_check = validate_numeric_ode_coefficients(euler_coeffs, 3, 4, operation);
385 if (!coeff_check) {
386 return ODESolutionResult::failure(coeff_check.error());
387 }
388
389 auto budget = context.consume_steps(euler_coeffs.size() * 20 + 20, operation);
390 if (!budget) {
391 return ODESolutionResult::failure(budget.error());
392 }
393
394 const bool nonzero_forcing = has_nonzero_forcing(forcing);
395 if (nonzero_forcing && unsupported_euler_forcing(forcing, euler_coeffs)) {
398 "non-homogeneous Euler ODE currently requires a constant forcing and nonzero y coefficient",
399 operation);
400 }
401
402 try {
403 auto solution_result = solve_euler_ode_impl(
404 euler_coeffs, nullptr, x, y, context);
405 if (!solution_result) {
406 return ODESolutionResult::failure(solution_result.error());
407 }
408 auto solution = std::move(solution_result.value());
409 if (nonzero_forcing && solution.general_solution) {
410 auto particular = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::divide(
411 forcing,
412 SymbolicExpr::number(euler_coeffs.back()))), transform_budget));
413 solution.general_solution = detail::make_expression_ptr(detail::normalize_node(detail::node(SymbolicExpr::add(
414 solution.general_solution, particular)), transform_budget));
415 }
416 return wrap_ode_solution(
417 std::move(solution),
419 operation);
420 } catch (const std::bad_alloc&) {
423 "allocation failed while solving Euler ODE",
424 operation);
425 } catch (const std::length_error& error) { return ODESolutionResult::failure(CasErrc::ResourceLimit, error.what(), "solve_euler_ode"); } catch (const std::exception& ex) {
428 ex.what(),
429 operation);
430 }
431} catch (const CasError& error) {
432 return ODESolutionResult::failure(error);
433} catch (const std::bad_alloc&) {
434 return ODESolutionResult::failure(CasErrc::ResourceLimit, "allocation failed", "solve_euler_ode");
435} catch (const std::length_error& error) {
436 return ODESolutionResult::failure(CasErrc::ResourceLimit, error.what(), "solve_euler_ode");
437} catch (const std::exception& error) {
438 return ODESolutionResult::failure(CasErrc::InternalInvariant, error.what(), "solve_euler_ode");
439}
440
442 const std::vector<double>& euler_coeffs,
443 const std::shared_ptr<SymbolicExpr>& forcing,
444 const std::string& x,
445 const std::string& y)
446{
448 return solve_euler_ode_checked(euler_coeffs, forcing, x, y, context);
449}
450
452 const std::vector<double>& euler_coeffs,
453 const std::shared_ptr<SymbolicExpr>& forcing,
454 const std::string& x,
455 const std::string&, ComputationContext& context)
456try {
457
458 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
459 (context).limits().max_ast_nodes, "transform");
460 const std::string operation = "solve_euler_ode";
461 ODESolution result;
463
464 if ((euler_coeffs.size() != 3 && euler_coeffs.size() != 4) ||
465 has_nonzero_forcing(forcing)) {
468 "Euler ODE is outside the implemented support domain",
469 operation);
470 }
471
472 std::vector<double> characteristic;
473 if (euler_coeffs.size() == 3) {
474 const double a = euler_coeffs[0];
475 const double b = euler_coeffs[1];
476 const double c = euler_coeffs[2];
477 characteristic = {a, b - a, c};
478 } else {
479 const double a = euler_coeffs[0];
480 const double b = euler_coeffs[1];
481 const double c = euler_coeffs[2];
482 const double d = euler_coeffs[3];
483 characteristic = {a, b - 3.0 * a, 2.0 * a - b + c, d};
484 }
485
486 auto roots = find_characteristic_roots(characteristic, operation);
487 if (!roots) {
489 }
490 result.general_solution =
492 if (result.general_solution) {
494 }
495 return Result<ODESolution>::success(std::move(result));
496} catch (const CasError& error) {
497 return Result<ODESolution>::failure(error);
498} catch (const std::bad_alloc&) {
499 return Result<ODESolution>::failure(CasErrc::ResourceLimit, "allocation failed", "solve_euler_ode");
500} catch (const std::length_error& error) {
501 return Result<ODESolution>::failure(CasErrc::ResourceLimit, error.what(), "solve_euler_ode");
502} catch (const std::exception& error) {
503 return Result<ODESolution>::failure(CasErrc::InternalInvariant, error.what(), "solve_euler_ode");
504}
505
506/**
507 * @internal
508 * @brief 检测非齐次项的类型,用于待定系数法.
509 *
510 * 支持的类型:
511 * - 多项式: x^n
512 * - 指数: e^(ax)
513 * - 三角: cos(bx), sin(bx)
514 */
515
516} // namespace LMCAS
CasError & error()
static Result failure(CasErrc code, std::string message, std::string operation={})
static Result success(T value)
std::shared_ptr<::LMCAS::SymbolicExpr > make_expression_ptr(SymbolicNodePtr root)
const SymbolicNodePtr & node(const ::LMCAS::SymbolicExpr &expression) noexcept
SymbolicNodePtr normalize_node(const SymbolicNodePtr &node, RewriteBudget &budget, const FactsQuery &facts, Domain domain)
Result< std::vector< CharRoot > > find_characteristic_roots(const std::vector< double > &coefficients, const std::string &operation)
使用数值方法求解特征多项式的所有根.
ODESolutionResult solve_higher_order_ode_checked(const std::vector< double > &coeffs, const std::shared_ptr< SymbolicExpr > &forcing, const std::string &x, const std::string &y, ComputationContext &context)
ODESolutionResult wrap_ode_solution(ODESolution solution, ODEType expected_method, const std::string &operation)
ODEType method_used
使用的求解方法
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
@ Euler
Euler (Cauchy-Euler) 方程
@ HigherOrder_ConstCoeff
高阶常系数线性
static bool has_nonzero_forcing(const std::shared_ptr< SymbolicExpr > &forcing)
const VectorField const VectorField & c
static std::shared_ptr< SymbolicExpr > build_euler_solution(const std::vector< CharRoot > &roots, const std::string &x, std::vector< std::string > &constants)
const MultiPoly & b
ODESolutionResult solve_euler_ode_checked(const std::vector< double > &euler_coeffs, const std::shared_ptr< SymbolicExpr > &forcing, const std::string &x, const std::string &y, ComputationContext &context)
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
static Result< ODESolution > solve_higher_order_ode_impl(const std::vector< double > &, const std::shared_ptr< SymbolicExpr > &, const std::string &, const std::string &, ComputationContext &context)
ExprSetResult roots(const ExprPtr &expression, const std::string &variable, ComputationContext &context, const SolveOptions &options)
std::shared_ptr< SymbolicExpr > general_solution
通解表达式
const std::string const ExprPtr & value
Result< void > validate_numeric_ode_coefficients(const std::vector< double > &coefficients, std::size_t minimum_size, std::size_t maximum_size, const std::string &operation)
std::vector< std::string > constants
任意常数名称列表 (C1, C2, ...)
Result< void > validate_ode_variables(const std::string &x, const std::string &y, ComputationContext &context, const std::string &operation)
static Result< ODESolution > solve_euler_ode_impl(const std::vector< double > &, const std::shared_ptr< SymbolicExpr > &, const std::string &, const std::string &, ComputationContext &context)
static bool is_representable_small_integer(double value)
将 double 值转为"干净"的数值表达式.
static bool unsupported_euler_forcing(const std::shared_ptr< SymbolicExpr > &forcing, const std::vector< double > &coefficients)
static std::shared_ptr< SymbolicExpr > build_homogeneous_solution(const std::vector< CharRoot > &roots, const std::string &x, std::vector< std::string > &constants)
根据特征根构造齐次通解.
static std::shared_ptr< SymbolicExpr > clean_number(double val)
Result< ODESolution > ODESolutionResult
多项式转换与精确符号多项式代数。
符号表达式主类 SymbolicExpr 及其工厂方法,运算接口.
内部 AST 定义。
统一 ODE 求解引擎:分类,分派,多种求解方法.