LMCAS
Lamina Computer Algebra System
载入中...
搜索中...
未找到
symbolic_ode.cpp
浏览该文件的文档.
2#include "symbolic_ode.hpp"
8#include "symbolic.hpp"
10#include "integrator.hpp"
11#include "lmmc/config.h"
12#include "lmmc/numeric.h"
13#include <cmath>
14#include <memory>
15#include <new>
16#include <stdexcept>
17#include <string>
18
19namespace LMCAS {
20
21namespace {
22
23constexpr const char* kSolveLinear2OdeOperation = "solve_linear2_ode";
24
25// Separation only describes regions where g(y) is finite and nonzero.
26// In that region reciprocal integer powers can be combined without extending
27// the domain of the original ODE, unlike unconditional normalization.
28std::shared_ptr<const SymbolicNode> reciprocal_on_regular_region(
29 const std::shared_ptr<const SymbolicNode>& node) {
30 if (const auto product = std::dynamic_pointer_cast<const MultiplyNode>(node)) {
31 std::vector<std::shared_ptr<const SymbolicNode>> factors;
32 factors.reserve(product->operands().size());
33 for (const auto& factor : product->operands()) {
34 factors.push_back(reciprocal_on_regular_region(factor));
35 }
36 return detail::make_node<MultiplyNode>(std::move(factors));
37 }
38 if (const auto power = std::dynamic_pointer_cast<const PowerNode>(node)) {
39 BigInt exponent;
41 std::dynamic_pointer_cast<const NumberNode>(power->exponent()), exponent)) {
44 }
45 }
47}
48
49}
50
51/// Check if the dependent variable is known Positive in the given context.
53 const std::string& y, const AssumptionContext* ctx, ComputationContext& context) {
54 if (!ctx) {
55 return false;
56 }
57 auto y_expr = SymbolicExpr::variable(y);
58 auto positive = ctx->is_positive_checked(*y_expr, context);
59 if (!positive) { return Result<bool>::failure(positive.error()); }
60 return positive.value() == Tribool::True;
61}
62
63/// Wrap an expression in abs() to signal positive-branch preference.
64static std::shared_ptr<SymbolicExpr> make_abs(std::shared_ptr<SymbolicExpr> expr) {
68 std::vector<std::shared_ptr<const SymbolicNode>>{LMCAS::detail::node(expr)}));
69}
70
71// solve_separable_ode
72
73ExprPtr solve_separable_ode(ExprPtr rhs, const std::string& x,
74 const std::string& y, const AssumptionContext* ctx) {
76 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
77 (context).limits().max_ast_nodes, "transform");
78
79 if (!is_separable(rhs, x, y)) {
80 return nullptr;
81 }
82
83 std::shared_ptr<SymbolicExpr> f;
84 std::shared_ptr<SymbolicExpr> g;
85 const auto& root = detail::node(rhs);
86 if (!expression_depends_on_variable(root, y)) {
87 f = rhs;
88 g = SymbolicExpr::number(1);
89 } else if (!expression_depends_on_variable(root, x)) {
90 f = SymbolicExpr::number(1);
91 g = rhs;
92 } else {
93 // The shared classifier guarantees a product of uncoupled factors here.
94 const auto& product = static_cast<const MultiplyNode&>(*root);
95 std::vector<std::shared_ptr<const SymbolicNode>> x_factors;
96 std::vector<std::shared_ptr<const SymbolicNode>> y_factors;
97 for (const auto& factor : product.operands()) {
98 auto& factors = expression_depends_on_variable(factor, y) ? y_factors : x_factors;
99 factors.push_back(factor);
100 }
103 }
104
105 auto inv_y = detail::make_expression_ptr(detail::normalize_node(reciprocal_on_regular_region(detail::node(g)), transform_budget));
106 Integrator integrator;
107 auto integrated_y = integrator.integrate_checked(*inv_y, y, context);
108 if (!integrated_y) { throw integrated_y.error(); }
109 auto int_y = std::make_shared<SymbolicExpr>(std::move(integrated_y.value()));
110 auto normalized_f = detail::make_expression_ptr(
111 detail::normalize_node(detail::node(f), transform_budget));
112 auto integrated_x = integrator.integrate_checked(*normalized_f, x, context);
113 if (!integrated_x) { throw integrated_x.error(); }
114 auto int_x = std::make_shared<SymbolicExpr>(std::move(integrated_x.value()));
116 int_y, SymbolicExpr::multiply(SymbolicExpr::number(-1), int_x))), transform_budget));
117
118 // Preserve the existing positive-context branch marker.
119 auto positive = dep_var_is_positive(y, ctx, context);
120 if (!positive) { throw positive.error(); }
121 if (positive.value()) {
122 result = make_abs(result);
123 }
124
125 return result;
126}
127
128
129// solve_linear1_ode
130
131ExprPtr solve_linear1_ode(ExprPtr Px, ExprPtr Qx, const std::string& x,
132 const std::string& y, const AssumptionContext* ctx) {
134 Integrator integrator;
135 auto integrated_p = integrator.integrate_checked(*Px, x, context);
136 if (!integrated_p) { throw integrated_p.error(); }
137 auto intP = std::make_shared<SymbolicExpr>(std::move(integrated_p.value()));
138 auto mu = SymbolicExpr::exp(intP);
139
140 auto Qmu = SymbolicExpr::multiply(Qx, mu);
141 auto integrated_q = integrator.integrate_checked(*Qmu, x, context);
142 if (!integrated_q) { throw integrated_q.error(); }
143 auto intQmu = std::make_shared<SymbolicExpr>(std::move(integrated_q.value()));
144
145 auto C = SymbolicExpr::variable("C");
146 auto num = SymbolicExpr::add(intQmu, C);
147 auto result = SymbolicExpr::divide(num, mu);
148
149 // When the dependent variable is known Positive, prefer positive branch.
150 auto positive = dep_var_is_positive(y, ctx, context);
151 if (!positive) { throw positive.error(); }
152 if (positive.value()) {
153 result = make_abs(result);
154 }
155
156 return result;
157}
158
159
160// solve_linear2_ode
161
162static std::shared_ptr<SymbolicExpr> linear2_root_basis(
163 const std::vector<ode_root_detail::CharRoot>& roots,
164 const std::string& x) {
165 auto variable = SymbolicExpr::variable(x);
166 auto solution = SymbolicExpr::number(0);
167 int constant_index = 1;
168 for (const auto& root : roots) {
169 auto exponential = SymbolicExpr::exp(
170 SymbolicExpr::multiply(
171 SymbolicExpr::number(root.real_part), variable));
172 if (root.is_complex) {
173 auto argument = SymbolicExpr::multiply(
174 SymbolicExpr::number(root.imag_part), variable);
175 auto cosine_basis = SymbolicExpr::multiply(
176 exponential, SymbolicExpr::cos(argument));
177 auto sine_basis = SymbolicExpr::multiply(
178 exponential, SymbolicExpr::sin(argument));
179 auto cosine_term = SymbolicExpr::multiply(
180 SymbolicExpr::variable(
181 "C" + std::to_string(constant_index++)),
182 cosine_basis);
183 auto sine_term = SymbolicExpr::multiply(
184 SymbolicExpr::variable(
185 "C" + std::to_string(constant_index++)),
186 sine_basis);
187 solution = SymbolicExpr::add(
188 solution, SymbolicExpr::add(cosine_term, sine_term));
189 continue;
190 }
191 for (int power = 0; power < root.multiplicity; ++power) {
192 auto basis = exponential;
193 if (power > 0) {
194 basis = SymbolicExpr::multiply(
195 SymbolicExpr::power(
196 variable, SymbolicExpr::number(power)),
197 exponential);
198 }
199 solution = SymbolicExpr::add(
200 solution,
201 SymbolicExpr::multiply(
202 SymbolicExpr::variable(
203 "C" + std::to_string(constant_index++)),
204 basis));
205 }
206 }
207 return solution;
208}
209
210static Result<std::shared_ptr<SymbolicExpr>>
212 double a, double b, double c,
213 const std::string& x,
214 const std::string& y,
215 const AssumptionContext* ctx, ComputationContext& context)
216try {
217
218 detail::RewriteBudget transform_budget(context, (context).limits().max_recursion_depth,
219 (context).limits().max_ast_nodes, "transform");
220 if (!std::isfinite(a) || !std::isfinite(b) || !std::isfinite(c)) {
223 "solve_linear2_ode: coefficients must be finite",
224 kSolveLinear2OdeOperation);
225 }
226 ExprPtr solution;
227 if (a == 0.0) {
228 if (b == 0.0) {
231 "solve_linear2_ode: leading and first-derivative coefficients are both zero",
232 kSolveLinear2OdeOperation);
233 }
234 const double normalized_c = c / b;
235 if (!std::isfinite(normalized_c)) {
238 "solve_linear2_ode: degenerate coefficient normalization is non-finite",
239 kSolveLinear2OdeOperation);
240 }
241 // 常系数退化方程以特征根 -c/b 构造指数基,沿用数值系数的求解语义。
242 solution = SymbolicExpr::multiply(SymbolicExpr::variable("C"),
243 SymbolicExpr::exp(SymbolicExpr::multiply(
244 SymbolicExpr::number(-normalized_c), SymbolicExpr::variable(x))));
245 } else {
247 {a, b, c}, kSolveLinear2OdeOperation);
248 if (!roots) {
250 }
251 solution = linear2_root_basis(roots.value(), x);
252 }
253 solution = detail::make_expression_ptr(detail::normalize_node(detail::node(solution), transform_budget));
254 auto positive = dep_var_is_positive(y, ctx, context);
255 if (!positive) { return ExpressionResult::failure(positive.error()); }
256 if (positive.value()) {
257 solution = make_abs(solution);
258 }
260 std::move(solution));
261} catch (const CasError& error) {
262 return Result<std::shared_ptr<SymbolicExpr>>::failure(error);
263} catch (const std::bad_alloc&) {
264 return Result<std::shared_ptr<SymbolicExpr>>::failure(CasErrc::ResourceLimit, "allocation failed", "solve_linear2_homogeneous");
265} catch (const std::length_error& error) {
266 return Result<std::shared_ptr<SymbolicExpr>>::failure(CasErrc::ResourceLimit, error.what(), "solve_linear2_homogeneous");
267} catch (const std::exception& error) {
268 return Result<std::shared_ptr<SymbolicExpr>>::failure(CasErrc::InternalInvariant, error.what(), "solve_linear2_homogeneous");
269}
270
271std::shared_ptr<SymbolicExpr> solve_linear2_ode(
272 double a, double b, double c,
273 std::shared_ptr<SymbolicExpr> fx,
274 const std::string& x,
275 const std::string& y,
276 const AssumptionContext* ctx
277) {
279 if (!fx || !LMCAS::detail::node(fx)) {
280 throw std::invalid_argument(
281 "solve_linear2_ode: forcing expression must not be null");
282 }
283 if (!fx->is_zero()) {
284 throw std::logic_error(
285 "solve_linear2_ode: non-homogeneous case is outside the current support domain");
286 }
287 auto solution = solve_linear2_homogeneous(a, b, c, x, y, ctx, context);
288 if (!solution) {
289 if (solution.error().code == CasErrc::InvalidArgument) {
290 throw std::invalid_argument(solution.error().message);
291 }
292 if (solution.error().code == CasErrc::NumericFailure) {
293 throw std::overflow_error(solution.error().message);
294 }
295 throw std::logic_error(solution.error().message);
296 }
297 return std::move(solution.value());
298}
299
301 double a, double b, double c,
302 std::shared_ptr<SymbolicExpr> fx,
303 const std::string& x,
304 const std::string& y,
306 const AssumptionContext* ctx
307) {
308 auto step = context.consume_steps(1, kSolveLinear2OdeOperation);
309 if (!step) {
310 return Result<std::shared_ptr<SymbolicExpr>>::failure(step.error());
311 }
312
313 if (!fx || !LMCAS::detail::node(fx)) {
316 "forcing expression must not be null",
317 kSolveLinear2OdeOperation);
318 }
319 if (x.empty() || y.empty()) {
322 "ODE variables must not be empty",
323 kSolveLinear2OdeOperation);
324 }
325
326 try {
327 if (!fx->is_zero()) {
330 "non-homogeneous second-order constant-coefficient ODEs are outside the current support domain",
331 kSolveLinear2OdeOperation);
332 }
333
334 return solve_linear2_homogeneous(a, b, c, x, y, ctx, context);
335 } catch (const std::invalid_argument& ex) {
337 CasErrc::InvalidArgument, ex.what(), kSolveLinear2OdeOperation);
338 } catch (const std::bad_alloc&) {
341 "ODE solving allocation failed",
342 kSolveLinear2OdeOperation);
343 } catch (const std::logic_error& ex) {
345 CasErrc::Inconclusive, ex.what(), kSolveLinear2OdeOperation);
346 } catch (const std::exception& ex) {
348 CasErrc::InternalInvariant, ex.what(), kSolveLinear2OdeOperation);
349 }
350}
351
353 double a, double b, double c,
354 std::shared_ptr<SymbolicExpr> fx,
355 const std::string& x,
356 const std::string& y,
357 const AssumptionContext* ctx
358) {
360 return solve_linear2_ode_checked(a, b, c, std::move(fx), x, y, context, ctx);
361}
362
363}
AssumptionContext class with scoped push/pop management.
乘法节点,表示多个操作数的乘积。
CasError & error()
static Result failure(CasErrc code, std::string message, std::string operation={})
std::shared_ptr< const SymbolicNode > reciprocal_on_regular_region(const std::shared_ptr< const SymbolicNode > &node)
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)
Result< std::vector< CharRoot > > find_characteristic_roots(const std::vector< double > &coefficients, const std::string &operation)
使用数值方法求解特征多项式的所有根.
std::shared_ptr< SymbolicExpr > num(int value)
const std::string & variable
const std::vector< Polynomial< BigInt > > const BigInt ComputationContext & context
bool try_get_integer_value(const std::shared_ptr< const NumberNode > &node, BigInt &value)
const VectorField const VectorField & c
bool expression_depends_on_variable(const detail::SymbolicNodePtr &expression, const std::string &variable, detail::RewriteBudget *budget)
static std::shared_ptr< SymbolicExpr > make_abs(std::shared_ptr< SymbolicExpr > expr)
Wrap an expression in abs() to signal positive-branch preference.
@ True
Property definitely holds
const MultiPoly & b
static std::shared_ptr< SymbolicExpr > linear2_root_basis(const std::vector< ode_root_detail::CharRoot > &roots, const std::string &x)
static Result< bool > dep_var_is_positive(const std::string &y, const AssumptionContext *ctx, ComputationContext &context)
Check if the dependent variable is known Positive in the given 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
std::shared_ptr< SymbolicExpr > solve_linear2_ode(double a, double b, double c, std::shared_ptr< SymbolicExpr > fx, const std::string &x, const std::string &y, const AssumptionContext *ctx)
Result< std::shared_ptr< SymbolicExpr > > solve_linear2_ode_checked(double a, double b, double c, std::shared_ptr< SymbolicExpr > fx, const std::string &x, const std::string &y, ComputationContext &context, const AssumptionContext *ctx)
std::shared_ptr< SymbolicExpr > ExprPtr
ExprPtr solve_separable_ode(ExprPtr rhs, const std::string &x, const std::string &y, const AssumptionContext *ctx)
const std::vector< FGLMPoly > & basis
ExprSetResult roots(const ExprPtr &expression, const std::string &variable, ComputationContext &context, const SolveOptions &options)
static Result< std::shared_ptr< SymbolicExpr > > solve_linear2_homogeneous(double a, double b, double c, const std::string &x, const std::string &y, const AssumptionContext *ctx, ComputationContext &context)
const ExprPtr & rhs
ExprPtr solve_linear1_ode(ExprPtr Px, ExprPtr Qx, const std::string &x, const std::string &y, const AssumptionContext *ctx)
const std::vector< IntegrationStep > Integrator & integrator
bool is_separable(const std::shared_ptr< SymbolicExpr > &rhs, const std::string &x, const std::string &y)
符号表达式主类 SymbolicExpr 及其工厂方法,运算接口.
内部 AST 定义。
常微分方程求解:可分离变量,一阶线性,二阶常系数.
统一 ODE 求解引擎:分类,分派,多种求解方法.