1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! Continuous creative telescoping: **Almkvist–Zeilberger**, the differential
//! twin of [`mod@super::zeilberger`].
//!
//! # Scope
//!
//! Where the discrete engine takes a proper hypergeometric summand `F(n,k)`,
//! the shift `k → k+1` and the difference operator `Δ_k`, this one takes a
//! *hyperexponential* integrand `F(n,x)`, the derivation `D_x`, and produces
//! the same shape of certificate:
//!
//! ```text
//! Σ_{i=0}^{J} a_i(n)·F(n+i, x) = D_x( R(n,x)·F(n,x) )
//! ```
//!
//! with polynomial coefficients `a_i(n)` (not all zero, `a_J ≢ 0`) and an exact
//! rational certificate `R ∈ Q(n)(x)`. That identity — and only that identity —
//! is what [`almkvist_zeilberger`] verifies exactly before returning.
//!
//! | discrete ([`mod@super::zeilberger`]) | continuous (here) |
//! |---|---|
//! | `Σ_k F(n,k)` | `∫_a^b F(n,x) dx` |
//! | shift `k → k+1`, `Δ_k` | derivation `D_x` |
//! | proper hypergeometric term | hyperexponential term ([`mod@hyperexp`]) |
//! | Gosper's algorithm | differential Gosper ([`mod@dgosper`]) |
//! | [`mod@super::boundary`] | [`mod@boundary`] |
//!
//! # Module layout
//!
//! - [`mod@hyperexp`] — recognition of
//! `F(n,x) = R(n,x)·wⁿ·exp(η(x))·∏ B_j(x)^(α_j n + β_j)` and its two exact
//! certificates: the logarithmic derivative `θ = ∂_x F / F ∈ Q(n)(x)` and the
//! shift ratios `F(n+i,x)/F(n,x) ∈ Q(n)(x)`.
//! - [`mod@rde`] — the shared inner solver. Both stages below are the *same*
//! parametric Risch differential equation `R' + θ·R = Σ_i a_i·r_i` solved by
//! undetermined coefficients over `Q(n)`; the only difference is whether the
//! right-hand side is fixed (`J = 0`, `a_0 = 1`) or carries unknowns.
//! - [`mod@dgosper`] — the indefinite case: decide whether `∫F dx = R·F` for a
//! rational `R`, and return it. This is `J = 0` with `a_0` normalised to `1`.
//! - [`mod@search`] — [`almkvist_zeilberger`] itself, ascending in `J` from `0`.
//! - [`mod@boundary`] — the continuous analogue of [`mod@super::boundary`]:
//! deciding whether `[R·F]_a^b` vanishes, so that the certificate becomes a
//! recurrence for `f(n) = ∫_a^b F(n,x) dx` rather than a statement about the
//! integrand alone.
//!
//! # The integral recurrence carries a hypothesis
//!
//! Integrating the certificate over `x ∈ [a,b]` gives
//!
//! ```text
//! Σ_i a_i(n)·f(n+i) = [ R(n,x)·F(n,x) ]_a^b, f(n) = ∫_a^b F(n,x) dx
//! ```
//!
//! so `Σ_i a_i(n)·f(n+i) = 0` holds **only when that boundary term vanishes**.
//! The certificate says nothing about it: `R·F` is a perfectly good
//! antiderivative of the left-hand side whether or not it happens to vanish at
//! the limits, and whether or not the integral converges there at all.
//! [`boundary::integral_boundary_status`] decides it three-valued, in the same
//! discipline as the discrete module — see its docs, and do not read
//! [`boundary::IntegralBoundaryStatus::Unknown`] as a vanishing boundary.
//!
//! # Honest limitations (read before relying on this)
//!
//! - **Integrand class**: hyperexponential in `x` *and* hypergeometric in `n`,
//! in the specific shape [`hyperexp::HyperExpTerm`] parses — a rational
//! `Q(n)(x)` prefactor times `wⁿ` (`w ∈ Q`) times `exp(η)` with `η ∈ Q(n)(x)`
//! times finitely many `B(x)^(α·n + β)` with `α ∈ Z`, `β ∈ Q`. Anything
//! outside it — `log x`, `sin x`, a *sum* of two hyperexponential terms, an
//! algebraic function that is not a rational power — is refused as
//! [`DiffTelescopingError::NotHyperexponential`], never approximated.
//! `exp(n·x)` is the instructive refusal: it *is* hyperexponential in `x`,
//! but `F(n+1,x)/F(n,x) = eˣ` is not rational, so no algorithm in this family
//! applies and [`DiffTelescopingError::NotHypergeometricInN`] says so
//! specifically rather than reporting a shape failure.
//! - **Formal, not analytic**: `B(x)^β` for non-integer `β` is treated as a
//! formal symbol whose logarithmic derivative is `β·B'/B`. The certificate is
//! an identity of formal hyperexponential expressions; reading it as an
//! identity of *functions* additionally requires a branch of `B^β` to be
//! fixed consistently on the interval in question. That is a real hypothesis
//! on e.g. `x^(1/2)` across `x = 0`, and it is stated rather than assumed.
//! - **Certificate denominator**: the ansatz is `R = P(x)/(D(x)^κ·B(x))` with
//! `D` the denominator of `θ`, `B` the common denominator of the shift ratios,
//! `deg P ≤ d` and `κ ≤ max_den_power`. The *support* of that denominator is
//! not a guess — a pole of `R` must be a pole of `θ` or of the right-hand
//! side, which is exactly `D` and `B` — but the *multiplicity* `κ` is a
//! bounded search, and one specific case pushes it up without bound: at a
//! simple pole of `θ` with residue `ρ`, `R` may have a pole of order `ρ`
//! whenever `ρ` is a positive integer (`∫x²·eˣ dx = (x²−2x+2)·eˣ` needs
//! `κ = 2`). With a *symbolic* `ρ ∈ Q(n)` — the usual case for `xⁿ·…` —
//! whether that resonance occurs is not decidable at all, and this module
//! does not pretend otherwise: it searches `κ` and refuses with
//! [`DiffTelescopingError::SearchExhausted`] when the bound runs out.
//! - **Minimal order, not minimal degree**: [`mod@search`] ascends in `J` from `0`
//! and tries every `(κ, d)` in bounds before moving on, so a returned order
//! *is* the least one reachable within `max_den_power`/`max_degree`. It is
//! not the cost-ordered iterative deepening [`mod@super::zeilberger`] uses, so
//! raising `max_degree` is not free the way it is there.
//! - **Boundary**: only constant (`n`-independent) limits are analysed, and
//! only the endpoint kinds [`mod@boundary`] documents. Convergence conditions on
//! `n` are *reported*, not silently assumed — see
//! [`boundary::IntegralBoundaryStatus::side_conditions`].
//! - **Rust-internal**: there is no PyO3 binding for any of this yet.
//!
//! Every certificate returned by any entry point in this module has been
//! re-derived and checked as an exact identity in `Q(n)(x)` first, exactly as
//! [`super::zeilberger::zeilberger()`] does. A successful call is a proof; an
//! unverifiable candidate is discarded and the search continues.
pub use ;
pub use ;
pub use ;
pub use ;
/// Search bounds for the continuous engine.
///
/// All three are upper *bounds*, not starting points: the search walks the
/// `(order, κ, degree)` grid from the cheapest corner, so raising any of them
/// only widens what can be found. Unlike [`mod@super::zeilberger`]'s
/// cost-ordered deepening, though, the walk here is plain ascending nested
/// loops, so raising `max_degree` genuinely does cost more on an input that
/// has no certificate at all.
use fmt;
/// Errors from the continuous (differential) creative-telescoping engine.
///
/// A separate type from [`super::HolonomicError`] for the same reason
/// [`super::telescoping2d::Telescoping2dError`] is: the public enums in this
/// subsystem are exhaustive, so sharing one would make every new engine a
/// major-version break, and a caller could not tell which engine refused.