1use crate::error::{LoopError, Result};
7use crate::types::{Vector3D, Matrix3D};
8use crate::types::NalgebraVec3;
9use nalgebra::Matrix3;
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12
13#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
15pub struct LipschitzParams {
16 pub lipschitz_constant: f64,
18 pub tolerance: f64,
20 pub max_iterations: usize,
22 pub adaptive_estimation: bool,
24 pub damping: f64,
26}
27
28impl Default for LipschitzParams {
29 fn default() -> Self {
30 Self {
31 lipschitz_constant: 0.9,
32 tolerance: 1e-12,
33 max_iterations: 10_000,
34 adaptive_estimation: true,
35 damping: 0.99,
36 }
37 }
38}
39
40impl LipschitzParams {
41 pub fn fast_convergence() -> Self {
43 Self {
44 lipschitz_constant: 0.5,
45 tolerance: 1e-9,
46 max_iterations: 1_000,
47 adaptive_estimation: true,
48 damping: 0.95,
49 }
50 }
51
52 pub fn high_precision() -> Self {
54 Self {
55 lipschitz_constant: 0.8,
56 tolerance: 1e-15,
57 max_iterations: 100_000,
58 adaptive_estimation: true,
59 damping: 0.999,
60 }
61 }
62
63 pub fn validate(&self) -> Result<()> {
65 if self.lipschitz_constant <= 0.0 || self.lipschitz_constant >= 1.0 {
66 return Err(LoopError::invalid_policy(
67 "Lipschitz constant must be in (0, 1) for convergence"
68 ));
69 }
70 if self.tolerance <= 0.0 {
71 return Err(LoopError::invalid_policy("Tolerance must be positive"));
72 }
73 if self.max_iterations == 0 {
74 return Err(LoopError::invalid_policy("Max iterations must be positive"));
75 }
76 if self.damping <= 0.0 || self.damping > 1.0 {
77 return Err(LoopError::invalid_policy("Damping must be in (0, 1]"));
78 }
79 Ok(())
80 }
81
82 pub fn convergence_rate(&self) -> f64 {
84 -self.lipschitz_constant.ln()
85 }
86
87 pub fn estimated_iterations(&self, initial_distance: f64) -> usize {
89 if initial_distance <= self.tolerance {
90 return 0;
91 }
92 let rate = self.convergence_rate();
93 if rate <= 0.0 {
94 return self.max_iterations;
95 }
96 ((initial_distance / self.tolerance).ln() / rate).ceil() as usize
97 }
98}
99
100#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
102pub enum LoopTopology {
103 FixedPoint,
105 Newton,
107 Secant,
109 Accelerated,
111 ConjugateGradient,
113 Custom,
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct ConvergenceResult {
120 pub converged: bool,
122 pub iterations: usize,
124 pub final_residual: f64,
126 pub estimated_lipschitz: f64,
128 pub convergence_rate: f64,
130 pub convergence_time_ns: u128,
132 pub residual_history: Vec<f64>,
134}
135
136pub struct LipschitzLoop {
138 params: LipschitzParams,
139 topology: LoopTopology,
140 state_history: VecDeque<NalgebraVec3>,
141 residual_history: VecDeque<f64>,
142 estimated_lipschitz: f64,
143 iteration_count: usize,
144}
145
146impl LipschitzLoop {
147 pub fn new(params: LipschitzParams, topology: LoopTopology) -> Result<Self> {
149 params.validate()?;
150
151 Ok(Self {
152 params,
153 topology,
154 state_history: VecDeque::with_capacity(1000),
155 residual_history: VecDeque::with_capacity(1000),
156 estimated_lipschitz: 0.0,
157 iteration_count: 0,
158 })
159 }
160
161 pub fn execute<F>(&mut self, mut f: F, initial_state: NalgebraVec3) -> Result<ConvergenceResult>
163 where
164 F: FnMut(NalgebraVec3) -> NalgebraVec3,
165 {
166 let start_time = std::time::Instant::now();
167 let mut current_state = initial_state;
168 let mut previous_state = initial_state;
169 self.state_history.clear();
170 self.residual_history.clear();
171 self.iteration_count = 0;
172
173 self.state_history.push_back(current_state);
175
176 for iteration in 0..self.params.max_iterations {
177 self.iteration_count = iteration;
178
179 let next_state = match self.topology {
181 LoopTopology::FixedPoint => self.fixed_point_step(&mut f, current_state)?,
182 LoopTopology::Newton => self.newton_step(&mut f, current_state, previous_state)?,
183 LoopTopology::Secant => self.secant_step(&mut f, current_state, previous_state)?,
184 LoopTopology::Accelerated => self.accelerated_step(&mut f, current_state)?,
185 LoopTopology::ConjugateGradient => self.conjugate_gradient_step(&mut f, current_state, previous_state)?,
186 LoopTopology::Custom => f(current_state), };
188
189 let residual = (next_state - current_state).norm();
191 self.residual_history.push_back(residual);
192
193 if self.params.adaptive_estimation && iteration > 0 {
195 self.update_lipschitz_estimate(current_state, next_state, previous_state);
196 }
197
198 if residual < self.params.tolerance {
200 return Ok(ConvergenceResult {
201 converged: true,
202 iterations: iteration + 1,
203 final_residual: residual,
204 estimated_lipschitz: self.estimated_lipschitz,
205 convergence_rate: self.calculate_convergence_rate(),
206 convergence_time_ns: start_time.elapsed().as_nanos(),
207 residual_history: self.residual_history.iter().copied().collect(),
208 });
209 }
210
211 if self.estimated_lipschitz > self.params.lipschitz_constant && iteration > 2 {
213 return Err(LoopError::lipschitz_violation(
214 self.estimated_lipschitz,
215 self.params.lipschitz_constant,
216 ));
217 }
218
219 previous_state = current_state;
221 current_state = next_state;
222 self.state_history.push_back(current_state);
223
224 if self.state_history.len() > 1000 {
226 self.state_history.pop_front();
227 }
228 if self.residual_history.len() > 1000 {
229 self.residual_history.pop_front();
230 }
231 }
232
233 Err(LoopError::convergence_failure(self.params.max_iterations))
235 }
236
237 fn fixed_point_step<F>(&self, f: &mut F, current: NalgebraVec3) -> Result<NalgebraVec3>
239 where
240 F: FnMut(NalgebraVec3) -> NalgebraVec3,
241 {
242 let new_state = f(current);
243 Ok(current + self.params.damping * (new_state - current))
244 }
245
246 fn newton_step<F>(&self, f: &mut F, current: NalgebraVec3, _previous: NalgebraVec3) -> Result<NalgebraVec3>
248 where
249 F: FnMut(NalgebraVec3) -> NalgebraVec3,
250 {
251 let residual = f(current) - current;
252
253 let h = 1e-8;
255 let mut jacobian = Matrix3::zeros();
256
257 for i in 0..3 {
258 let mut perturbed = current;
259 perturbed[i] += h;
260 let f_perturbed = f(perturbed) - perturbed;
261
262 for j in 0..3 {
263 jacobian[(j, i)] = (f_perturbed[j] - residual[j]) / h;
264 }
265 }
266
267 for i in 0..3 {
269 jacobian[(i, i)] -= 1.0;
270 }
271
272 let delta = match jacobian.lu().solve(&(-residual)) {
274 Some(solution) => solution,
275 None => {
276 -self.params.damping * residual
278 }
279 };
280
281 Ok(current + delta)
282 }
283
284 fn secant_step<F>(&self, f: &mut F, current: NalgebraVec3, previous: NalgebraVec3) -> Result<NalgebraVec3>
286 where
287 F: FnMut(NalgebraVec3) -> NalgebraVec3,
288 {
289 let f_current = f(current);
290 let f_previous = f(previous);
291
292 let denominator = f_current - f_previous;
294 if denominator.norm() < 1e-12 {
295 return self.fixed_point_step(f, current);
297 }
298
299 let secant_direction = (current - previous).component_div(&denominator);
300 let residual = f_current - current;
301
302 Ok(current - secant_direction.component_mul(&residual) * self.params.damping)
303 }
304
305 fn accelerated_step<F>(&self, f: &mut F, current: NalgebraVec3) -> Result<NalgebraVec3>
307 where
308 F: FnMut(NalgebraVec3) -> NalgebraVec3,
309 {
310 if self.state_history.len() < 3 {
311 return self.fixed_point_step(f, current);
312 }
313
314 let f_current = f(current);
315
316 let m = self.state_history.len().min(3);
318 if m >= 2 {
319 let x_prev = self.state_history[self.state_history.len() - 2];
320 let f_prev = f(x_prev);
321
322 let df = f_current - f_prev;
323 let dx = current - x_prev;
324
325 if df.norm() > 1e-12 {
326 let alpha = dx.dot(&df) / df.norm_squared();
327 let alpha_clamped = alpha.clamp(0.0, 1.0);
328
329 return Ok(current + alpha_clamped * (f_current - current));
330 }
331 }
332
333 self.fixed_point_step(f, current)
334 }
335
336 fn conjugate_gradient_step<F>(&self, f: &mut F, current: NalgebraVec3, previous: NalgebraVec3) -> Result<NalgebraVec3>
338 where
339 F: FnMut(NalgebraVec3) -> NalgebraVec3,
340 {
341 let gradient = f(current) - current;
342
343 if self.state_history.len() < 2 {
344 return Ok(current - self.params.damping * gradient);
345 }
346
347 let prev_gradient = f(previous) - previous;
349 let direction = if prev_gradient.norm() > 1e-12 {
350 let beta = gradient.norm_squared() / prev_gradient.norm_squared();
351 let prev_direction = current - previous;
352 gradient + beta * prev_direction
353 } else {
354 gradient
355 };
356
357 Ok(current - self.params.damping * direction)
358 }
359
360 fn update_lipschitz_estimate(&mut self, current: NalgebraVec3, next: NalgebraVec3, previous: NalgebraVec3) {
362 if (current - previous).norm() < 1e-12 {
363 return;
364 }
365
366 let output_distance = (next - current).norm();
367 let input_distance = (current - previous).norm();
368
369 let local_lipschitz = output_distance / input_distance;
370
371 const ALPHA: f64 = 0.1;
373 if self.estimated_lipschitz == 0.0 {
374 self.estimated_lipschitz = local_lipschitz;
375 } else {
376 self.estimated_lipschitz = (1.0 - ALPHA) * self.estimated_lipschitz + ALPHA * local_lipschitz;
377 }
378 }
379
380 fn calculate_convergence_rate(&self) -> f64 {
382 if self.residual_history.len() < 2 {
383 return 0.0;
384 }
385
386 let n = self.residual_history.len();
387 let initial_residual = self.residual_history[0];
388 let final_residual = self.residual_history[n - 1];
389
390 if initial_residual <= 0.0 || final_residual <= 0.0 {
391 return 0.0;
392 }
393
394 -(final_residual / initial_residual).ln() / (n as f64)
395 }
396
397 pub fn params(&self) -> &LipschitzParams {
399 &self.params
400 }
401
402 pub fn update_params(&mut self, params: LipschitzParams) -> Result<()> {
404 params.validate()?;
405 self.params = params;
406 Ok(())
407 }
408
409 pub fn topology(&self) -> &LoopTopology {
411 &self.topology
412 }
413
414 pub fn set_topology(&mut self, topology: LoopTopology) {
416 self.topology = topology;
417 }
418
419 pub fn state_history(&self) -> &VecDeque<NalgebraVec3> {
421 &self.state_history
422 }
423
424 pub fn residual_history(&self) -> &VecDeque<f64> {
426 &self.residual_history
427 }
428
429 pub fn estimated_lipschitz(&self) -> f64 {
431 self.estimated_lipschitz
432 }
433
434 pub fn reset(&mut self) {
436 self.state_history.clear();
437 self.residual_history.clear();
438 self.estimated_lipschitz = 0.0;
439 self.iteration_count = 0;
440 }
441
442 pub fn execute_with_criteria<F, C>(
444 &mut self,
445 mut f: F,
446 initial_state: NalgebraVec3,
447 mut convergence_check: C,
448 ) -> Result<ConvergenceResult>
449 where
450 F: FnMut(NalgebraVec3) -> NalgebraVec3,
451 C: FnMut(NalgebraVec3, NalgebraVec3, usize) -> bool,
452 {
453 let start_time = std::time::Instant::now();
454 let mut current_state = initial_state;
455 let mut previous_state = initial_state;
456 self.state_history.clear();
457 self.residual_history.clear();
458 self.iteration_count = 0;
459
460 self.state_history.push_back(current_state);
461
462 for iteration in 0..self.params.max_iterations {
463 self.iteration_count = iteration;
464
465 let next_state = match self.topology {
466 LoopTopology::FixedPoint => self.fixed_point_step(&mut f, current_state)?,
467 LoopTopology::Newton => self.newton_step(&mut f, current_state, previous_state)?,
468 LoopTopology::Secant => self.secant_step(&mut f, current_state, previous_state)?,
469 LoopTopology::Accelerated => self.accelerated_step(&mut f, current_state)?,
470 LoopTopology::ConjugateGradient => self.conjugate_gradient_step(&mut f, current_state, previous_state)?,
471 LoopTopology::Custom => f(current_state),
472 };
473
474 let residual = (next_state - current_state).norm();
475 self.residual_history.push_back(residual);
476
477 if convergence_check(current_state, next_state, iteration) {
479 return Ok(ConvergenceResult {
480 converged: true,
481 iterations: iteration + 1,
482 final_residual: residual,
483 estimated_lipschitz: self.estimated_lipschitz,
484 convergence_rate: self.calculate_convergence_rate(),
485 convergence_time_ns: start_time.elapsed().as_nanos(),
486 residual_history: self.residual_history.iter().copied().collect(),
487 });
488 }
489
490 if self.params.adaptive_estimation && iteration > 0 {
491 self.update_lipschitz_estimate(current_state, next_state, previous_state);
492 }
493
494 previous_state = current_state;
495 current_state = next_state;
496 self.state_history.push_back(current_state);
497
498 if self.state_history.len() > 1000 {
499 self.state_history.pop_front();
500 }
501 if self.residual_history.len() > 1000 {
502 self.residual_history.pop_front();
503 }
504 }
505
506 Err(LoopError::convergence_failure(self.params.max_iterations))
507 }
508
509 pub fn analyze_stability<F>(&self, f: F, fixed_point: NalgebraVec3) -> Result<StabilityAnalysis>
511 where
512 F: Fn(NalgebraVec3) -> NalgebraVec3,
513 {
514 let h = 1e-8;
516 let mut jacobian = Matrix3::zeros();
517
518 for i in 0..3 {
519 let mut perturbed = fixed_point;
520 perturbed[i] += h;
521 let f_perturbed = f(perturbed);
522 let f_fixed = f(fixed_point);
523
524 for j in 0..3 {
525 jacobian[(j, i)] = (f_perturbed[j] - f_fixed[j]) / h;
526 }
527 }
528
529 let eigenvalues = jacobian.eigenvalues().unwrap_or_default();
531 let max_eigenvalue = eigenvalues.iter()
532 .map(|e| e.abs())
533 .fold(0.0f64, f64::max);
534
535 let stability = if max_eigenvalue < 1.0 {
536 StabilityType::Stable
537 } else if max_eigenvalue == 1.0 {
538 StabilityType::Marginal
539 } else {
540 StabilityType::Unstable
541 };
542
543 Ok(StabilityAnalysis {
544 stability,
545 max_eigenvalue,
546 spectral_radius: max_eigenvalue,
547 jacobian,
548 eigenvalues: eigenvalues.iter().map(|e| e.abs()).collect(),
549 })
550 }
551}
552
553#[derive(Clone, Debug)]
555pub struct StabilityAnalysis {
556 pub stability: StabilityType,
558 pub max_eigenvalue: f64,
560 pub spectral_radius: f64,
562 pub jacobian: Matrix3D,
564 pub eigenvalues: Vec<f64>,
566}
567
568#[derive(Clone, Debug, PartialEq, Eq)]
570pub enum StabilityType {
571 Stable,
573 Marginal,
575 Unstable,
577}
578
579pub struct LoopFunctionFactory;
581
582impl LoopFunctionFactory {
583 pub fn scalar_function(target: f64, step_size: f64) -> impl Fn(NalgebraVec3) -> NalgebraVec3 {
585 move |x: NalgebraVec3| {
586 let gradient = NalgebraVec3::new(
587 x[0] - target,
588 x[1] - target,
589 x[2] - target,
590 );
591 x - step_size * gradient
592 }
593 }
594
595 pub fn quadratic_bowl(center: NalgebraVec3, curvature: f64) -> impl Fn(NalgebraVec3) -> NalgebraVec3 {
597 move |x: NalgebraVec3| {
598 let gradient = curvature * (x - center);
599 x - 0.1 * gradient
600 }
601 }
602
603 pub fn rosenbrock_like(a: f64, b: f64) -> impl Fn(NalgebraVec3) -> NalgebraVec3 {
605 move |x: NalgebraVec3| {
606 let grad_x = -2.0 * a * (1.0 - x[0]) - 4.0 * b * x[0] * (x[1] - x[0] * x[0]);
607 let grad_y = 2.0 * b * (x[1] - x[0] * x[0]);
608 let grad_z = 2.0 * (x[2] - 1.0);
609
610 NalgebraVec3::new(
611 x[0] - 0.001 * grad_x,
612 x[1] - 0.001 * grad_y,
613 x[2] - 0.001 * grad_z,
614 )
615 }
616 }
617
618 pub fn attractor_function(attractor_point: NalgebraVec3, strength: f64) -> impl Fn(NalgebraVec3) -> NalgebraVec3 {
620 move |x: NalgebraVec3| {
621 let direction = attractor_point - x;
622 x + strength * direction
623 }
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use approx::assert_relative_eq;
631
632 #[test]
633 fn test_lipschitz_params_validation() {
634 let params = LipschitzParams::default();
635 assert!(params.validate().is_ok());
636
637 let bad_params = LipschitzParams {
638 lipschitz_constant: 1.5, ..params
640 };
641 assert!(bad_params.validate().is_err());
642 }
643
644 #[test]
645 fn test_convergence_rate_estimation() {
646 let params = LipschitzParams::default();
647 let rate = params.convergence_rate();
648 assert!(rate > 0.0);
649
650 let iterations = params.estimated_iterations(10.0);
651 assert!(iterations > 0);
652 }
653
654 #[test]
655 fn test_fixed_point_convergence() {
656 let params = LipschitzParams::fast_convergence();
657 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
658
659 let function = |x: NalgebraVec3| 0.5 * x;
661 let initial_state = NalgebraVec3::new(10.0, 10.0, 10.0);
662
663 let result = loop_solver.execute(function, initial_state).unwrap();
664
665 assert!(result.converged);
666 assert!(result.final_residual < 1e-9);
667 assert!(result.iterations > 0);
668 }
669
670 #[test]
671 fn test_newton_convergence() {
672 let params = LipschitzParams::default();
673 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::Newton).unwrap();
674
675 let target = NalgebraVec3::new(1.0, 2.0, 3.0);
677 let function = LoopFunctionFactory::quadratic_bowl(target, 0.1);
678 let initial_state = NalgebraVec3::new(5.0, 5.0, 5.0);
679
680 let result = loop_solver.execute(function, initial_state).unwrap();
681
682 assert!(result.converged);
683 assert!(result.estimated_lipschitz > 0.0);
684 }
685
686 #[test]
687 fn test_accelerated_convergence() {
688 let params = LipschitzParams::default();
689 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::Accelerated).unwrap();
690
691 let function = LoopFunctionFactory::scalar_function(0.0, 0.1);
692 let initial_state = NalgebraVec3::new(5.0, 5.0, 5.0);
693
694 let result = loop_solver.execute(function, initial_state).unwrap();
695
696 assert!(result.converged);
697 assert!(!result.residual_history.is_empty());
698 }
699
700 #[test]
701 fn test_custom_convergence_criteria() {
702 let params = LipschitzParams::default();
703 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
704
705 let function = |x: NalgebraVec3| 0.9 * x;
706 let initial_state = NalgebraVec3::new(1.0, 1.0, 1.0);
707
708 let convergence_check = |_current: NalgebraVec3, next: NalgebraVec3, _iter: usize| {
710 next.iter().any(|&x| x.abs() < 0.1)
711 };
712
713 let result = loop_solver.execute_with_criteria(function, initial_state, convergence_check).unwrap();
714
715 assert!(result.converged);
716 }
717
718 #[test]
719 fn test_lipschitz_violation_detection() {
720 let params = LipschitzParams {
721 lipschitz_constant: 0.5, adaptive_estimation: true,
723 ..LipschitzParams::default()
724 };
725 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
726
727 let function = |x: NalgebraVec3| 1.1 * x;
729 let initial_state = NalgebraVec3::new(1.0, 1.0, 1.0);
730
731 let result = loop_solver.execute(function, initial_state);
732
733 match result {
735 Err(LoopError::LipschitzViolation { .. }) => (),
736 _ => panic!("Expected Lipschitz violation"),
737 }
738 }
739
740 #[test]
741 fn test_stability_analysis() {
742 let params = LipschitzParams::default();
743 let loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
744
745 let function = |x: NalgebraVec3| 0.5 * x;
747 let fixed_point = NalgebraVec3::zeros();
748
749 let analysis = loop_solver.analyze_stability(function, fixed_point).unwrap();
750
751 assert_eq!(analysis.stability, StabilityType::Stable);
752 assert!(analysis.max_eigenvalue < 1.0);
753 }
754
755 #[test]
756 fn test_loop_function_factory() {
757 let function = LoopFunctionFactory::scalar_function(0.0, 0.1);
758 let input = NalgebraVec3::new(1.0, 1.0, 1.0);
759 let output = function(input);
760
761 assert!(output.norm() < input.norm());
763
764 let quadratic = LoopFunctionFactory::quadratic_bowl(NalgebraVec3::zeros(), 0.1);
765 let quad_output = quadratic(input);
766 assert!(quad_output.norm() < input.norm());
767 }
768
769 #[test]
770 fn test_rosenbrock_function() {
771 let function = LoopFunctionFactory::rosenbrock_like(1.0, 100.0);
772 let input = NalgebraVec3::new(0.0, 0.0, 0.0);
773 let output = function(input);
774
775 assert!(output.iter().all(|x| x.is_finite()));
777 }
778
779 #[test]
780 fn test_attractor_function() {
781 let attractor_point = NalgebraVec3::new(1.0, 2.0, 3.0);
782 let function = LoopFunctionFactory::attractor_function(attractor_point, 0.1);
783 let input = NalgebraVec3::zeros();
784 let output = function(input);
785
786 let distance_before = (input - attractor_point).norm();
788 let distance_after = (output - attractor_point).norm();
789 assert!(distance_after < distance_before);
790 }
791
792 #[test]
793 fn test_topology_switching() {
794 let params = LipschitzParams::default();
795 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
796
797 assert_eq!(*loop_solver.topology(), LoopTopology::FixedPoint);
798
799 loop_solver.set_topology(LoopTopology::Newton);
800 assert_eq!(*loop_solver.topology(), LoopTopology::Newton);
801 }
802
803 #[test]
804 fn test_parameter_updates() {
805 let initial_params = LipschitzParams::default();
806 let mut loop_solver = LipschitzLoop::new(initial_params.clone(), LoopTopology::FixedPoint).unwrap();
807
808 let new_params = LipschitzParams::fast_convergence();
809 loop_solver.update_params(new_params.clone()).unwrap();
810
811 assert_eq!(loop_solver.params().lipschitz_constant, new_params.lipschitz_constant);
812 }
813
814 #[test]
815 fn test_reset_functionality() {
816 let params = LipschitzParams::default();
817 let mut loop_solver = LipschitzLoop::new(params, LoopTopology::FixedPoint).unwrap();
818
819 let function = |x: NalgebraVec3| 0.9 * x;
821 let initial_state = NalgebraVec3::new(1.0, 1.0, 1.0);
822 let _ = loop_solver.execute(function, initial_state);
823
824 assert!(!loop_solver.state_history().is_empty());
825 assert!(!loop_solver.residual_history().is_empty());
826
827 loop_solver.reset();
828
829 assert!(loop_solver.state_history().is_empty());
830 assert!(loop_solver.residual_history().is_empty());
831 assert_eq!(loop_solver.estimated_lipschitz(), 0.0);
832 }
833}