1#[cfg(test)]
2mod test;
3
4use super::{Integrable, reconstruct_or_err};
5use crate::math::{
6 Derivative, Differentiable, Quantity, Scalar,
7 integrate::{ButcherTableau, IntegrationError},
8 optimize::{EqualityConstraint, FirstOrderRootFinding, SecondOrderOptimization},
9 sparse::SparseSolver,
10};
11use std::ops::{AddAssign, Mul};
12
13pub(super) fn rkmk_stage_slopes_into<Field, Tab, T>(
26 rate: &mut impl FnMut(Quantity<T>, &Field::Point) -> Result<Derivative<Field::Increment, T>, String>,
27 point: &Field::Point,
28 t: Quantity<T>,
29 dt: Quantity<T>,
30 slopes: &mut Vec<Field::Increment>,
31 first_rate: Option<&Derivative<Field::Increment, T>>,
32) -> Result<Option<Derivative<Field::Increment, T>>, IntegrationError>
33where
34 Field: Integrable,
35 Tab: ButcherTableau,
36 Field::Point: Clone,
37 Field::Increment: Clone + Differentiable<T>,
38 T: Copy,
39 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
40 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
41{
42 slopes.clear();
43 slopes.reserve(Tab::STAGES);
44 let mut carry = None;
45 for i in 0..Tab::STAGES {
46 let sigma = if i == 0 {
47 None
48 } else {
49 let mut accumulated = slopes[0].clone() * Tab::A[i][0];
50 for (j, slope) in slopes.iter().enumerate().take(i).skip(1) {
51 accumulated += slope.clone() * Tab::A[i][j];
52 }
53 Some(accumulated)
54 };
55 let stage_point = match &sigma {
56 Some(sigma) => reconstruct_or_err::<Field>(point, sigma)?,
57 None => point.clone(),
58 };
59 let increment = match (i, first_rate) {
60 (0, Some(seed)) => seed * dt,
61 _ => {
62 let raw = rate(t + dt * Tab::C[i], &stage_point)?;
63 let increment = &raw * dt;
64 if Tab::FSAL && i + 1 == Tab::STAGES {
65 carry = Some(raw);
66 }
67 increment
68 }
69 };
70 slopes.push(match &sigma {
71 Some(sigma) => Field::dexpinv(sigma, increment),
72 None => increment,
73 });
74 }
75 Ok(carry)
76}
77
78pub(super) fn weight<P>(slopes: &[P], weights: &[Scalar]) -> P
79where
80 P: Clone + Mul<Scalar, Output = P> + AddAssign,
81{
82 let mut sum = slopes[0].clone() * weights[0];
83 for (i, slope) in slopes.iter().enumerate().skip(1) {
84 sum += slope.clone() * weights[i];
85 }
86 sum
87}
88
89pub fn rkmk_step<Field, Tab, T>(
94 rate: &mut impl FnMut(Quantity<T>, &Field::Point) -> Result<Derivative<Field::Increment, T>, String>,
95 point: &Field::Point,
96 t: Quantity<T>,
97 dt: Quantity<T>,
98 scratch: &mut Vec<Field::Increment>,
99) -> Result<Field::Point, IntegrationError>
100where
101 Field: Integrable,
102 Tab: ButcherTableau,
103 Field::Point: Clone,
104 Field::Increment: Clone + Differentiable<T>,
105 T: Copy,
106 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
107 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
108{
109 rkmk_stage_slopes_into::<Field, Tab, T>(rate, point, t, dt, scratch, None)?;
110 reconstruct_or_err::<Field>(point, &weight(scratch, Tab::B))
111}
112
113#[allow(clippy::too_many_arguments, clippy::type_complexity)]
133pub fn rkmk_dae_step<Field, Tab, Z, T>(
134 rate: &mut impl FnMut(
135 Quantity<T>,
136 &Field::Point,
137 &Z,
138 ) -> Result<Derivative<Field::Increment, T>, String>,
139 solve: &mut impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<Z, String>,
140 point: &Field::Point,
141 z: &Z,
142 t: Quantity<T>,
143 dt: Quantity<T>,
144 scratch: &mut Vec<Field::Increment>,
145 first_rate: Option<&Derivative<Field::Increment, T>>,
146) -> Result<(Field::Point, Z, Option<Derivative<Field::Increment, T>>), IntegrationError>
147where
148 Field: Integrable,
149 Tab: ButcherTableau,
150 Field::Point: Clone,
151 Field::Increment: Clone + Differentiable<T>,
152 Z: Clone,
153 T: Copy,
154 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
155 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
156{
157 let (z_stage, carry) = rkmk_dae_stage_slopes_into::<Field, Tab, Z, T>(
158 rate, solve, point, z, t, dt, scratch, first_rate,
159 )?;
160 let advanced = reconstruct_or_err::<Field>(point, &weight(scratch, Tab::B))?;
161 let z_final = solve(t + dt, &advanced, &z_stage)?;
162 Ok((advanced, z_final, carry))
163}
164
165#[allow(clippy::too_many_arguments, clippy::type_complexity)]
175pub fn rkmk_dae_step_first_order_root<Field, Tab, F, J, Z, T>(
176 rate: &mut impl FnMut(
177 Quantity<T>,
178 &Field::Point,
179 &Z,
180 ) -> Result<Derivative<Field::Increment, T>, String>,
181 mut function: impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<F, String>,
182 mut jacobian: impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<J, String>,
183 solver: &impl FirstOrderRootFinding<F, J, Z>,
184 point: &Field::Point,
185 z: &Z,
186 t: Quantity<T>,
187 dt: Quantity<T>,
188 scratch: &mut Vec<Field::Increment>,
189 first_rate: Option<&Derivative<Field::Increment, T>>,
190 mut equality_constraint: impl FnMut(Quantity<T>) -> EqualityConstraint,
191) -> Result<(Field::Point, Z, Option<Derivative<Field::Increment, T>>), IntegrationError>
192where
193 Field: Integrable,
194 Tab: ButcherTableau,
195 Field::Point: Clone,
196 Field::Increment: Clone + Differentiable<T>,
197 Z: Clone,
198 T: Copy,
199 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
200 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
201{
202 let mut solve = |t: Quantity<T>, point: &Field::Point, z_guess: &Z| -> Result<Z, String> {
203 Ok(solver.root(
204 |z| function(t, point, z),
205 |z| jacobian(t, point, z),
206 z_guess.clone(),
207 equality_constraint(t),
208 None,
209 )?)
210 };
211 rkmk_dae_step::<Field, Tab, Z, T>(rate, &mut solve, point, z, t, dt, scratch, first_rate)
212}
213
214#[allow(clippy::too_many_arguments, clippy::type_complexity)]
221pub fn rkmk_dae_step_second_order_minimize<Field, Tab, F, J, H, Z, T>(
222 rate: &mut impl FnMut(
223 Quantity<T>,
224 &Field::Point,
225 &Z,
226 ) -> Result<Derivative<Field::Increment, T>, String>,
227 mut function: impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<F, String>,
228 mut jacobian: impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<J, String>,
229 mut hessian: impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<H, String>,
230 solver: &impl SecondOrderOptimization<F, J, H, Z>,
231 point: &Field::Point,
232 z: &Z,
233 t: Quantity<T>,
234 dt: Quantity<T>,
235 scratch: &mut Vec<Field::Increment>,
236 first_rate: Option<&Derivative<Field::Increment, T>>,
237 mut equality_constraint: impl FnMut(Quantity<T>) -> EqualityConstraint,
238 sparse: Option<SparseSolver>,
239) -> Result<(Field::Point, Z, Option<Derivative<Field::Increment, T>>), IntegrationError>
240where
241 Field: Integrable,
242 Tab: ButcherTableau,
243 Field::Point: Clone,
244 Field::Increment: Clone + Differentiable<T>,
245 Z: Clone,
246 T: Copy,
247 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
248 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
249{
250 let mut solve = |t: Quantity<T>, point: &Field::Point, z_guess: &Z| -> Result<Z, String> {
251 Ok(solver.minimize(
252 |z| function(t, point, z),
253 |z| jacobian(t, point, z),
254 |z| hessian(t, point, z),
255 z_guess.clone(),
256 equality_constraint(t),
257 sparse.clone(),
258 )?)
259 };
260 rkmk_dae_step::<Field, Tab, Z, T>(rate, &mut solve, point, z, t, dt, scratch, first_rate)
261}
262
263#[allow(clippy::too_many_arguments, clippy::type_complexity)]
270pub(super) fn rkmk_dae_stage_slopes_into<Field, Tab, Z, T>(
271 rate: &mut impl FnMut(
272 Quantity<T>,
273 &Field::Point,
274 &Z,
275 ) -> Result<Derivative<Field::Increment, T>, String>,
276 solve: &mut impl FnMut(Quantity<T>, &Field::Point, &Z) -> Result<Z, String>,
277 point: &Field::Point,
278 z: &Z,
279 t: Quantity<T>,
280 dt: Quantity<T>,
281 slopes: &mut Vec<Field::Increment>,
282 first_rate: Option<&Derivative<Field::Increment, T>>,
283) -> Result<(Z, Option<Derivative<Field::Increment, T>>), IntegrationError>
284where
285 Field: Integrable,
286 Tab: ButcherTableau,
287 Field::Point: Clone,
288 Field::Increment: Clone + Differentiable<T>,
289 Z: Clone,
290 T: Copy,
291 Quantity<T>: Mul<Scalar, Output = Quantity<T>>,
292 for<'a> &'a Derivative<Field::Increment, T>: Mul<Quantity<T>, Output = Field::Increment>,
293{
294 slopes.clear();
295 slopes.reserve(Tab::STAGES);
296 let mut z_stage = z.clone();
297 let mut carry = None;
298 for i in 0..Tab::STAGES {
299 let sigma = if i == 0 {
300 None
301 } else {
302 let mut accumulated = slopes[0].clone() * Tab::A[i][0];
303 for (j, slope) in slopes.iter().enumerate().take(i).skip(1) {
304 accumulated += slope.clone() * Tab::A[i][j];
305 }
306 Some(accumulated)
307 };
308 let stage_point = match &sigma {
309 Some(sigma) => reconstruct_or_err::<Field>(point, sigma)?,
310 None => point.clone(),
311 };
312 let t_stage = t + dt * Tab::C[i];
313 let increment = match (i, first_rate) {
314 (0, Some(seed)) => seed * dt,
315 _ => {
316 z_stage = solve(t_stage, &stage_point, &z_stage)?;
317 let raw = rate(t_stage, &stage_point, &z_stage)?;
318 let increment = &raw * dt;
319 if Tab::FSAL && i + 1 == Tab::STAGES {
320 carry = Some(raw);
321 }
322 increment
323 }
324 };
325 slopes.push(match &sigma {
326 Some(sigma) => Field::dexpinv(sigma, increment),
327 None => increment,
328 });
329 }
330 Ok((z_stage, carry))
331}