Skip to main content

rust_robotics_control/
rear_wheel_feedback.rs

1//! Rear Wheel Feedback path tracking controller
2//!
3//! A path tracking controller based on rear wheel feedback steering control
4//! that uses heading error and lateral error for computing the steering angle.
5//!
6//! Ref:
7//!     - PythonRobotics: <https://github.com/AtsushiSakai/PythonRobotics>
8//!     - B. Paden, M. Cap, S. Z. Yong, D. Yershov and E. Frazzoli,
9//!       "A Survey of Motion Planning and Control Techniques Adopted in Self-Driving Vehicles"
10
11use rust_robotics_core::{ControlInput, Path2D, PathTracker, Point2D, State2D};
12use std::f64::consts::PI;
13
14/// Vehicle state for Rear Wheel Feedback Controller
15#[derive(Debug, Clone, Copy)]
16pub struct VehicleState {
17    pub x: f64,
18    pub y: f64,
19    pub yaw: f64,
20    pub v: f64,
21    pub wheelbase: f64,
22}
23
24impl VehicleState {
25    pub fn new(x: f64, y: f64, yaw: f64, v: f64, wheelbase: f64) -> Self {
26        VehicleState {
27            x,
28            y,
29            yaw,
30            v,
31            wheelbase,
32        }
33    }
34
35    /// Update vehicle state using bicycle model kinematics
36    pub fn update(&mut self, a: f64, delta: f64, dt: f64) {
37        self.x += self.v * self.yaw.cos() * dt;
38        self.y += self.v * self.yaw.sin() * dt;
39        self.yaw += self.v / self.wheelbase * delta.tan() * dt;
40        self.v += a * dt;
41    }
42
43    /// Get rear axle position (same as state position for rear wheel reference)
44    pub fn rear_axle(&self) -> (f64, f64) {
45        (self.x, self.y)
46    }
47
48    pub fn to_state2d(&self) -> State2D {
49        State2D::new(self.x, self.y, self.yaw, self.v)
50    }
51}
52
53impl From<State2D> for VehicleState {
54    fn from(s: State2D) -> Self {
55        VehicleState::new(s.x, s.y, s.yaw, s.v, 2.9) // default wheelbase
56    }
57}
58
59/// Configuration for Rear Wheel Feedback Controller
60#[derive(Debug, Clone)]
61pub struct RearWheelFeedbackConfig {
62    /// Heading error gain (KTH)
63    pub kth: f64,
64    /// Lateral error gain (KE)
65    pub ke: f64,
66    /// Vehicle wheelbase \[m\]
67    pub wheelbase: f64,
68    /// Speed proportional gain
69    pub kp: f64,
70    /// Goal distance threshold \[m\]
71    pub goal_threshold: f64,
72    /// Maximum steering angle \[rad\]
73    pub max_steer: f64,
74}
75
76impl Default for RearWheelFeedbackConfig {
77    fn default() -> Self {
78        Self {
79            kth: 1.0,
80            ke: 0.5,
81            wheelbase: 2.9,
82            kp: 1.0,
83            goal_threshold: 0.5,
84            max_steer: 45.0_f64.to_radians(),
85        }
86    }
87}
88
89/// Rear Wheel Feedback path tracking controller
90pub struct RearWheelFeedbackController {
91    config: RearWheelFeedbackConfig,
92    path: Path2D,
93    path_yaw: Vec<f64>,
94    path_curvature: Vec<f64>,
95    last_target_idx: usize,
96}
97
98impl RearWheelFeedbackController {
99    /// Create a new Rear Wheel Feedback controller
100    pub fn new(config: RearWheelFeedbackConfig) -> Self {
101        RearWheelFeedbackController {
102            config,
103            path: Path2D::new(),
104            path_yaw: Vec::new(),
105            path_curvature: Vec::new(),
106            last_target_idx: 0,
107        }
108    }
109
110    /// Create with simplified parameters (legacy interface)
111    pub fn with_params(kth: f64, ke: f64, wheelbase: f64) -> Self {
112        let config = RearWheelFeedbackConfig {
113            kth,
114            ke,
115            wheelbase,
116            ..Default::default()
117        };
118        Self::new(config)
119    }
120
121    /// Set the reference path
122    pub fn set_path(&mut self, path: Path2D) {
123        self.path_yaw = self.compute_path_yaw(&path);
124        self.path_curvature = self.compute_path_curvature(&path);
125        self.path = path;
126        self.last_target_idx = 0;
127    }
128
129    /// Set the reference path with pre-computed yaw angles and curvatures
130    pub fn set_path_with_info(&mut self, path: Path2D, yaw: Vec<f64>, curvature: Vec<f64>) {
131        self.path = path;
132        self.path_yaw = yaw;
133        self.path_curvature = curvature;
134        self.last_target_idx = 0;
135    }
136
137    /// Get the current reference path
138    pub fn get_path(&self) -> &Path2D {
139        &self.path
140    }
141
142    /// Compute yaw angles from path points
143    fn compute_path_yaw(&self, path: &Path2D) -> Vec<f64> {
144        path.yaw_profile()
145    }
146
147    /// Compute curvature from path points
148    fn compute_path_curvature(&self, path: &Path2D) -> Vec<f64> {
149        let n = path.len();
150        if n < 3 {
151            return vec![0.0; n];
152        }
153
154        let mut curvature = Vec::with_capacity(n);
155
156        // First point
157        curvature.push(0.0);
158
159        // Middle points - compute curvature using three consecutive points
160        for i in 1..n - 1 {
161            let p0 = &path.points[i - 1];
162            let p1 = &path.points[i];
163            let p2 = &path.points[i + 1];
164
165            let dx1 = p1.x - p0.x;
166            let dy1 = p1.y - p0.y;
167            let dx2 = p2.x - p1.x;
168            let dy2 = p2.y - p1.y;
169
170            let ds1 = (dx1 * dx1 + dy1 * dy1).sqrt();
171            let ds2 = (dx2 * dx2 + dy2 * dy2).sqrt();
172
173            if ds1 > 1e-6 && ds2 > 1e-6 {
174                let yaw1 = dy1.atan2(dx1);
175                let yaw2 = dy2.atan2(dx2);
176                let dyaw = Self::normalize_angle(yaw2 - yaw1);
177                let ds = (ds1 + ds2) / 2.0;
178                curvature.push(dyaw / ds);
179            } else {
180                curvature.push(0.0);
181            }
182        }
183
184        // Last point
185        curvature.push(*curvature.last().unwrap_or(&0.0));
186
187        curvature
188    }
189
190    /// Normalize angle to [-PI, PI]
191    fn normalize_angle(mut angle: f64) -> f64 {
192        while angle > PI {
193            angle -= 2.0 * PI;
194        }
195        while angle < -PI {
196            angle += 2.0 * PI;
197        }
198        angle
199    }
200
201    /// Find target index and compute lateral error
202    fn calc_target_index(&self, state: &VehicleState) -> (usize, f64) {
203        let (rx, ry) = state.rear_axle();
204        let query = Point2D::new(rx, ry);
205        let min_idx = self
206            .path
207            .nearest_point_index_from(query, self.last_target_idx)
208            .unwrap_or(0);
209
210        // Calculate lateral error (signed distance from path)
211        let target_point = &self.path.points[min_idx];
212        let dx = rx - target_point.x;
213        let dy = ry - target_point.y;
214
215        // Lateral error with sign (positive = left of path, negative = right)
216        let path_yaw = if min_idx < self.path_yaw.len() {
217            self.path_yaw[min_idx]
218        } else {
219            0.0
220        };
221
222        // Cross product to determine sign
223        let error = -path_yaw.sin() * dx + path_yaw.cos() * dy;
224
225        (min_idx, error)
226    }
227
228    /// Compute steering angle using rear wheel feedback control law
229    ///
230    /// Control law:
231    /// omega = v * k * cos(th_e) / (1.0 - k * e) - KTH * |v| * th_e - KE * v * sin(th_e) * e / th_e
232    /// delta = atan2(L * omega / v, 1.0)
233    pub fn compute_steering(&mut self, state: &VehicleState) -> f64 {
234        if self.path.is_empty() {
235            return 0.0;
236        }
237
238        let (mut target_idx, e) = self.calc_target_index(state);
239
240        // Don't go backwards on path
241        if self.last_target_idx >= target_idx {
242            target_idx = self.last_target_idx;
243        }
244        self.last_target_idx = target_idx;
245
246        // Get path yaw and curvature at target point
247        let yaw_ref = if target_idx < self.path_yaw.len() {
248            self.path_yaw[target_idx]
249        } else {
250            *self.path_yaw.last().unwrap_or(&0.0)
251        };
252
253        let k = if target_idx < self.path_curvature.len() {
254            self.path_curvature[target_idx]
255        } else {
256            *self.path_curvature.last().unwrap_or(&0.0)
257        };
258
259        // Heading error
260        let th_e = Self::normalize_angle(state.yaw - yaw_ref);
261
262        // Compute angular velocity using rear wheel feedback control law
263        let v = state.v;
264        let v_abs = v.abs().max(0.1); // Avoid division by zero
265
266        // Handle small heading errors to avoid numerical issues
267        let th_e_safe = if th_e.abs() < 1e-6 {
268            1e-6 * th_e.signum().max(1.0)
269        } else {
270            th_e
271        };
272
273        // Rear wheel feedback control formula
274        let denom = 1.0 - k * e;
275        let denom_safe = if denom.abs() < 0.01 {
276            0.01 * denom.signum().max(1.0)
277        } else {
278            denom
279        };
280
281        let omega = v * k * th_e.cos() / denom_safe
282            - self.config.kth * v_abs * th_e
283            - self.config.ke * v * th_e.sin() * e / th_e_safe;
284
285        // Compute steering angle
286        let delta = if v.abs() > 0.01 {
287            (self.config.wheelbase * omega / v).atan()
288        } else {
289            0.0
290        };
291
292        // Clamp to maximum steering angle
293        delta.clamp(-self.config.max_steer, self.config.max_steer)
294    }
295
296    /// Proportional speed control
297    pub fn compute_acceleration(&self, target_speed: f64, current_speed: f64) -> f64 {
298        self.config.kp * (target_speed - current_speed)
299    }
300
301    /// Check if goal is reached
302    pub fn is_goal_reached_vehicle(&self, state: &VehicleState) -> bool {
303        if let Some(goal) = self.path.points.last() {
304            let dx = state.x - goal.x;
305            let dy = state.y - goal.y;
306            (dx * dx + dy * dy).sqrt() < self.config.goal_threshold
307        } else {
308            true
309        }
310    }
311
312    /// Legacy planning interface - simulate path tracking
313    pub fn planning(
314        &mut self,
315        waypoints: Vec<(f64, f64)>,
316        target_speed: f64,
317        ds: f64,
318    ) -> Option<Vec<(f64, f64)>> {
319        if waypoints.len() < 2 {
320            return None;
321        }
322
323        // Generate spline path
324        let ax: Vec<f64> = waypoints.iter().map(|p| p.0).collect();
325        let ay: Vec<f64> = waypoints.iter().map(|p| p.1).collect();
326        let (cx, cy, cyaw, ck, _) = calc_spline_course(&ax, &ay, ds);
327
328        // Set path
329        let path = Path2D::from_points(
330            cx.iter()
331                .zip(cy.iter())
332                .map(|(&x, &y)| Point2D::new(x, y))
333                .collect(),
334        );
335        self.set_path_with_info(path, cyaw, ck);
336
337        // Initialize state
338        let mut state = VehicleState::new(
339            waypoints[0].0,
340            waypoints[0].1,
341            self.path_yaw.first().copied().unwrap_or(0.0),
342            0.0,
343            self.config.wheelbase,
344        );
345
346        let mut trajectory = vec![(state.x, state.y)];
347        let dt = 0.1;
348        let t_max = 100.0;
349        let mut time = 0.0;
350
351        while time < t_max {
352            let ai = self.compute_acceleration(target_speed, state.v);
353            let di = self.compute_steering(&state);
354            state.update(ai, di, dt);
355            time += dt;
356
357            trajectory.push((state.x, state.y));
358
359            if self.is_goal_reached_vehicle(&state) {
360                break;
361            }
362        }
363
364        Some(trajectory)
365    }
366}
367
368impl PathTracker for RearWheelFeedbackController {
369    fn compute_control(&mut self, current_state: &State2D, path: &Path2D) -> ControlInput {
370        // Set path if different
371        if self.path.len() != path.len() {
372            self.set_path(path.clone());
373        }
374
375        let vehicle_state = VehicleState::new(
376            current_state.x,
377            current_state.y,
378            current_state.yaw,
379            current_state.v,
380            self.config.wheelbase,
381        );
382
383        let delta = self.compute_steering(&vehicle_state);
384
385        // Compute speed control (assume constant target speed)
386        let target_speed = 5.0; // m/s
387        let v = current_state.v + self.compute_acceleration(target_speed, current_state.v) * 0.1;
388
389        // Convert steering angle to angular velocity
390        let omega = v * delta.tan() / self.config.wheelbase;
391
392        ControlInput::new(v, omega)
393    }
394
395    fn is_goal_reached(&self, current_state: &State2D, goal: Point2D) -> bool {
396        let dx = current_state.x - goal.x;
397        let dy = current_state.y - goal.y;
398        (dx * dx + dy * dy).sqrt() < self.config.goal_threshold
399    }
400}
401
402// Cubic spline helper functions for path generation
403
404fn calc_spline_course(x: &[f64], y: &[f64], ds: f64) -> SplineCourse {
405    let sp = CubicSpline2D::new(x, y);
406    let mut s = 0.0;
407    let mut course_x = Vec::new();
408    let mut course_y = Vec::new();
409    let mut course_yaw = Vec::new();
410    let mut course_k = Vec::new();
411    let mut course_s = Vec::new();
412
413    let s_max = *sp.s.last().unwrap() - ds;
414    while s < s_max {
415        let (ix, iy) = sp.calc_position(s);
416        let iyaw = sp.calc_yaw(s);
417        let ik = sp.calc_curvature(s);
418        course_x.push(ix);
419        course_y.push(iy);
420        course_yaw.push(iyaw);
421        course_k.push(ik);
422        course_s.push(s);
423        s += ds;
424    }
425
426    (course_x, course_y, course_yaw, course_k, course_s)
427}
428
429struct CubicSpline {
430    a: Vec<f64>,
431    b: Vec<f64>,
432    c: Vec<f64>,
433    d: Vec<f64>,
434    x: Vec<f64>,
435}
436
437type SplineCourse = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>);
438
439impl CubicSpline {
440    fn new(x: &[f64], y: &[f64]) -> Self {
441        let n = x.len();
442        let mut h = vec![0.0; n - 1];
443        for i in 0..n - 1 {
444            h[i] = x[i + 1] - x[i];
445        }
446
447        let mut a = vec![0.0; n];
448        let mut b = vec![0.0; n];
449        let mut c = vec![0.0; n];
450        let mut d = vec![0.0; n];
451
452        a[..n].copy_from_slice(&y[..n]);
453
454        let mut alpha = vec![0.0; n - 1];
455        for i in 1..n - 1 {
456            alpha[i] = 3.0 * (a[i + 1] - a[i]) / h[i] - 3.0 * (a[i] - a[i - 1]) / h[i - 1];
457        }
458
459        let mut l = vec![1.0; n];
460        let mut mu = vec![0.0; n];
461        let mut z = vec![0.0; n];
462
463        for i in 1..n - 1 {
464            l[i] = 2.0 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1];
465            mu[i] = h[i] / l[i];
466            z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
467        }
468
469        for j in (0..n - 1).rev() {
470            c[j] = z[j] - mu[j] * c[j + 1];
471            b[j] = (a[j + 1] - a[j]) / h[j] - h[j] * (c[j + 1] + 2.0 * c[j]) / 3.0;
472            d[j] = (c[j + 1] - c[j]) / (3.0 * h[j]);
473        }
474
475        CubicSpline {
476            a,
477            b,
478            c,
479            d,
480            x: x.to_vec(),
481        }
482    }
483
484    fn calc(&self, t: f64) -> f64 {
485        if t < self.x[0] {
486            return self.a[0];
487        } else if t > self.x[self.x.len() - 1] {
488            return self.a[self.a.len() - 1];
489        }
490
491        let mut i = self.search_index(t);
492        if i >= self.x.len() - 1 {
493            i = self.x.len() - 2;
494        }
495
496        let dx = t - self.x[i];
497        self.a[i] + self.b[i] * dx + self.c[i] * dx * dx + self.d[i] * dx * dx * dx
498    }
499
500    fn calc_d(&self, t: f64) -> f64 {
501        if t < self.x[0] {
502            return self.b[0];
503        } else if t > self.x[self.x.len() - 1] {
504            return self.b[self.b.len() - 1];
505        }
506
507        let mut i = self.search_index(t);
508        if i >= self.x.len() - 1 {
509            i = self.x.len() - 2;
510        }
511
512        let dx = t - self.x[i];
513        self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx * dx
514    }
515
516    fn calc_dd(&self, t: f64) -> f64 {
517        if t < self.x[0] {
518            return 2.0 * self.c[0];
519        } else if t > self.x[self.x.len() - 1] {
520            return 2.0 * self.c[self.c.len() - 1];
521        }
522
523        let mut i = self.search_index(t);
524        if i >= self.x.len() - 1 {
525            i = self.x.len() - 2;
526        }
527
528        let dx = t - self.x[i];
529        2.0 * self.c[i] + 6.0 * self.d[i] * dx
530    }
531
532    fn search_index(&self, x: f64) -> usize {
533        for i in 0..self.x.len() - 1 {
534            if self.x[i] <= x && x <= self.x[i + 1] {
535                return i;
536            }
537        }
538        self.x.len() - 2
539    }
540}
541
542struct CubicSpline2D {
543    s: Vec<f64>,
544    sx: CubicSpline,
545    sy: CubicSpline,
546}
547
548impl CubicSpline2D {
549    fn new(x: &[f64], y: &[f64]) -> Self {
550        let mut s = vec![0.0];
551        for i in 1..x.len() {
552            let dx = x[i] - x[i - 1];
553            let dy = y[i] - y[i - 1];
554            s.push(s[i - 1] + (dx * dx + dy * dy).sqrt());
555        }
556
557        let sx = CubicSpline::new(&s, x);
558        let sy = CubicSpline::new(&s, y);
559
560        CubicSpline2D { s, sx, sy }
561    }
562
563    fn calc_position(&self, s: f64) -> (f64, f64) {
564        let x = self.sx.calc(s);
565        let y = self.sy.calc(s);
566        (x, y)
567    }
568
569    fn calc_curvature(&self, s: f64) -> f64 {
570        let dx = self.sx.calc_d(s);
571        let ddx = self.sx.calc_dd(s);
572        let dy = self.sy.calc_d(s);
573        let ddy = self.sy.calc_dd(s);
574        let denom = (dx * dx + dy * dy).powf(1.5);
575        if denom.abs() < 1e-10 {
576            0.0
577        } else {
578            (ddy * dx - ddx * dy) / denom
579        }
580    }
581
582    fn calc_yaw(&self, s: f64) -> f64 {
583        let dx = self.sx.calc_d(s);
584        let dy = self.sy.calc_d(s);
585        dy.atan2(dx)
586    }
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    #[test]
594    fn test_rear_wheel_feedback_creation() {
595        let config = RearWheelFeedbackConfig::default();
596        let controller = RearWheelFeedbackController::new(config);
597        assert!(controller.path.is_empty());
598    }
599
600    #[test]
601    fn test_rear_wheel_feedback_config_defaults() {
602        let config = RearWheelFeedbackConfig::default();
603        assert!((config.kth - 1.0).abs() < 1e-10);
604        assert!((config.ke - 0.5).abs() < 1e-10);
605        assert!((config.wheelbase - 2.9).abs() < 1e-10);
606    }
607
608    #[test]
609    fn test_rear_wheel_feedback_set_path() {
610        let mut controller = RearWheelFeedbackController::with_params(1.0, 0.5, 2.9);
611        let path = Path2D::from_points(vec![
612            Point2D::new(0.0, 0.0),
613            Point2D::new(1.0, 0.0),
614            Point2D::new(2.0, 0.0),
615        ]);
616        controller.set_path(path);
617        assert_eq!(controller.get_path().len(), 3);
618        assert_eq!(controller.path_yaw.len(), 3);
619        assert_eq!(controller.path_curvature.len(), 3);
620    }
621
622    #[test]
623    fn test_rear_wheel_feedback_normalize_angle() {
624        assert!((RearWheelFeedbackController::normalize_angle(3.0 * PI) - PI).abs() < 0.01);
625        assert!((RearWheelFeedbackController::normalize_angle(-3.0 * PI) + PI).abs() < 0.01);
626        assert!((RearWheelFeedbackController::normalize_angle(0.5)).abs() - 0.5 < 0.01);
627    }
628
629    #[test]
630    fn test_rear_wheel_feedback_steering_straight_path() {
631        let mut controller = RearWheelFeedbackController::with_params(1.0, 0.5, 2.9);
632        let path = Path2D::from_points(vec![
633            Point2D::new(0.0, 0.0),
634            Point2D::new(10.0, 0.0),
635            Point2D::new(20.0, 0.0),
636            Point2D::new(30.0, 0.0),
637        ]);
638        controller.set_path(path);
639
640        // Vehicle on path, heading along path
641        let state = VehicleState::new(5.0, 0.0, 0.0, 5.0, 2.9);
642        let steering = controller.compute_steering(&state);
643
644        // Should have near-zero steering for vehicle on path
645        assert!(steering.abs() < 0.1);
646    }
647
648    #[test]
649    fn test_rear_wheel_feedback_steering_lateral_error() {
650        let mut controller = RearWheelFeedbackController::with_params(1.0, 0.5, 2.9);
651        let path = Path2D::from_points(vec![
652            Point2D::new(0.0, 0.0),
653            Point2D::new(10.0, 0.0),
654            Point2D::new(20.0, 0.0),
655            Point2D::new(30.0, 0.0),
656        ]);
657        controller.set_path(path);
658
659        let state = VehicleState::new(5.0, 2.0, 0.1, 5.0, 2.9);
660        let steering = controller.compute_steering(&state);
661
662        assert!(
663            steering < 0.0,
664            "Steering should be negative to correct errors, got: {}",
665            steering
666        );
667    }
668
669    #[test]
670    fn test_rear_wheel_feedback_planning() {
671        let mut controller = RearWheelFeedbackController::with_params(1.0, 0.5, 2.9);
672        let waypoints = vec![(0.0, 0.0), (50.0, 0.0), (100.0, 0.0)];
673
674        let result = controller.planning(waypoints, 5.0, 0.5);
675        assert!(result.is_some());
676        assert!(!result.unwrap().is_empty());
677    }
678
679    #[test]
680    fn test_rear_wheel_feedback_curved_path() {
681        let mut controller = RearWheelFeedbackController::with_params(1.0, 0.5, 2.9);
682        let waypoints = vec![(0.0, 0.0), (10.0, 0.0), (20.0, 5.0), (30.0, 10.0)];
683
684        let result = controller.planning(waypoints, 3.0, 0.5);
685        assert!(result.is_some());
686
687        let trajectory = result.unwrap();
688        assert!(!trajectory.is_empty());
689
690        // Check that trajectory ends near goal
691        let last = trajectory.last().unwrap();
692        let dx = last.0 - 30.0;
693        let dy = last.1 - 10.0;
694        let dist = (dx * dx + dy * dy).sqrt();
695        assert!(dist < 2.0, "Final distance to goal: {}", dist);
696    }
697
698    #[test]
699    fn test_vehicle_state_update() {
700        let mut state = VehicleState::new(0.0, 0.0, 0.0, 1.0, 2.9);
701        state.update(0.0, 0.0, 1.0);
702
703        // Should move forward 1m with no steering
704        assert!((state.x - 1.0).abs() < 0.01);
705        assert!(state.y.abs() < 0.01);
706        assert!(state.yaw.abs() < 0.01);
707    }
708
709    #[test]
710    fn test_path_tracker_trait() {
711        let mut controller = RearWheelFeedbackController::new(RearWheelFeedbackConfig::default());
712        let path = Path2D::from_points(vec![
713            Point2D::new(0.0, 0.0),
714            Point2D::new(10.0, 0.0),
715            Point2D::new(20.0, 0.0),
716        ]);
717
718        let state = State2D::new(5.0, 0.0, 0.0, 5.0);
719        let control = controller.compute_control(&state, &path);
720
721        // Control input should be reasonable
722        assert!(control.v.is_finite());
723        assert!(control.omega.is_finite());
724    }
725}