1use crate::types::{Index, Number};
33
34pub trait PerturbationSink {
41 fn append_info(&self, s: &str);
43 fn set_regu_x(&self, v: Number);
46 fn iter_count(&self) -> Index {
48 -1
49 }
50 fn debug(&self, _msg: &str) {}
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum TrialStatus {
58 NoTest,
59 DcEq0DxEq0,
60 DcGt0DxEq0,
61 DcEq0DxGt0,
62 DcGt0DxGt0,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum DegenType {
68 NotDegenerate,
69 Degenerate,
70 NotYetDetermined,
71}
72
73#[derive(Debug, Clone)]
76pub struct PdPerturbationHandler {
77 pub delta_xs_max: Number,
79 pub delta_xs_min: Number,
80 pub delta_xs_first_inc_fact: Number,
81 pub delta_xs_inc_fact: Number,
82 pub delta_xs_dec_fact: Number,
83 pub delta_xs_init: Number,
84 pub delta_cd_val: Number,
85 pub delta_cd_exp: Number,
86 pub perturb_always_cd: bool,
87 pub reset_last: bool,
88 pub degen_iters_max: Index,
89
90 pub delta_x_curr: Number,
92 pub delta_s_curr: Number,
93 pub delta_c_curr: Number,
94 pub delta_d_curr: Number,
95 pub delta_x_last: Number,
96 pub delta_s_last: Number,
97 pub delta_c_last: Number,
98 pub delta_d_last: Number,
99 pub get_deltas_for_wrong_inertia_called: bool,
100 pub hess_degenerate: DegenType,
101 pub jac_degenerate: DegenType,
102 pub degen_iters: Index,
103 pub test_status: TrialStatus,
104}
105
106impl Default for PdPerturbationHandler {
107 fn default() -> Self {
108 Self {
109 delta_xs_max: 1e20,
110 delta_xs_min: 1e-20,
111 delta_xs_first_inc_fact: 100.0,
112 delta_xs_inc_fact: 8.0,
113 delta_xs_dec_fact: 1.0 / 3.0,
114 delta_xs_init: 1e-4,
115 delta_cd_val: 1e-8,
116 delta_cd_exp: 0.25,
117 perturb_always_cd: false,
118 reset_last: false,
119 degen_iters_max: 3,
120 delta_x_curr: 0.0,
121 delta_s_curr: 0.0,
122 delta_c_curr: 0.0,
123 delta_d_curr: 0.0,
124 delta_x_last: 0.0,
125 delta_s_last: 0.0,
126 delta_c_last: 0.0,
127 delta_d_last: 0.0,
128 get_deltas_for_wrong_inertia_called: false,
129 hess_degenerate: DegenType::NotYetDetermined,
130 jac_degenerate: DegenType::NotYetDetermined,
131 degen_iters: 0,
132 test_status: TrialStatus::NoTest,
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq)]
139pub struct Deltas {
140 pub delta_x: Number,
141 pub delta_s: Number,
142 pub delta_c: Number,
143 pub delta_d: Number,
144}
145
146impl PdPerturbationHandler {
147 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn set_perturb_always_cd(&mut self, on: bool) {
154 self.perturb_always_cd = on;
155 self.jac_degenerate = if on {
156 DegenType::NotDegenerate
157 } else {
158 DegenType::NotYetDetermined
159 };
160 }
161
162 pub fn consider_new_system(
167 &mut self,
168 mu: Number,
169 ip_data: Option<&dyn PerturbationSink>,
170 ) -> Option<Deltas> {
171 self.finalize_test(ip_data);
172
173 if self.reset_last {
176 self.delta_x_last = self.delta_x_curr;
177 self.delta_s_last = self.delta_s_curr;
178 self.delta_c_last = self.delta_c_curr;
179 self.delta_d_last = self.delta_d_curr;
180 } else {
181 if self.delta_x_curr > 0.0 {
182 self.delta_x_last = self.delta_x_curr;
183 }
184 if self.delta_s_curr > 0.0 {
185 self.delta_s_last = self.delta_s_curr;
186 }
187 if self.delta_c_curr > 0.0 {
188 self.delta_c_last = self.delta_c_curr;
189 }
190 if self.delta_d_curr > 0.0 {
191 self.delta_d_last = self.delta_d_curr;
192 }
193 }
194
195 let undet = matches!(self.hess_degenerate, DegenType::NotYetDetermined)
196 || matches!(self.jac_degenerate, DegenType::NotYetDetermined);
197 self.test_status = if undet {
198 if self.perturb_always_cd {
199 TrialStatus::DcGt0DxEq0
200 } else {
201 TrialStatus::DcEq0DxEq0
202 }
203 } else {
204 TrialStatus::NoTest
205 };
206
207 let mut delta_c = if matches!(self.jac_degenerate, DegenType::Degenerate) {
208 let v = self.delta_cd(mu);
209 self.delta_c_curr = v;
210 append_info(ip_data, "l");
211 v
212 } else if self.perturb_always_cd {
213 let v = self.delta_cd(mu);
214 self.delta_c_curr = v;
215 v
216 } else {
217 self.delta_c_curr = 0.0;
218 0.0
219 };
220 let mut delta_d = delta_c;
221 self.delta_d_curr = delta_d;
222
223 let mut delta_x = 0.0;
224 let mut delta_s = 0.0;
225
226 if matches!(self.hess_degenerate, DegenType::Degenerate) {
227 self.delta_x_curr = 0.0;
228 self.delta_s_curr = 0.0;
229 if !self.get_deltas_for_wrong_inertia(
230 &mut delta_x,
231 &mut delta_s,
232 &mut delta_c,
233 &mut delta_d,
234 ip_data,
235 ) {
236 return None;
237 }
238 }
239
240 self.delta_x_curr = delta_x;
241 self.delta_s_curr = delta_s;
242 self.delta_c_curr = delta_c;
243 self.delta_d_curr = delta_d;
244 set_info_regu_x(ip_data, delta_x);
245 self.get_deltas_for_wrong_inertia_called = false;
246
247 Some(Deltas {
248 delta_x,
249 delta_s,
250 delta_c,
251 delta_d,
252 })
253 }
254
255 pub fn perturb_for_singular(
258 &mut self,
259 mu: Number,
260 ip_data: Option<&dyn PerturbationSink>,
261 ) -> Option<Deltas> {
262 let mut delta_x = 0.0;
263 let mut delta_s = 0.0;
264 let mut delta_c = 0.0;
265 let mut delta_d = 0.0;
266
267 let probe_intact = match self.test_status {
280 TrialStatus::DcEq0DxEq0 => self.delta_x_curr == 0.0 && self.delta_c_curr == 0.0,
281 TrialStatus::DcGt0DxEq0 => self.delta_x_curr == 0.0 && self.delta_c_curr > 0.0,
282 TrialStatus::DcEq0DxGt0 => self.delta_x_curr > 0.0 && self.delta_c_curr == 0.0,
283 TrialStatus::DcGt0DxGt0 | TrialStatus::NoTest => true,
284 };
285 if !probe_intact {
286 self.test_status = TrialStatus::NoTest;
287 }
288
289 let undet = probe_intact
290 && (matches!(self.hess_degenerate, DegenType::NotYetDetermined)
291 || matches!(self.jac_degenerate, DegenType::NotYetDetermined));
292 if undet {
293 match self.test_status {
294 TrialStatus::DcEq0DxEq0 => {
295 debug_assert!(self.delta_x_curr == 0.0 && self.delta_c_curr == 0.0);
296 if matches!(self.jac_degenerate, DegenType::NotYetDetermined) {
297 let v = self.delta_cd(mu);
298 self.delta_c_curr = v;
299 self.delta_d_curr = v;
300 self.test_status = TrialStatus::DcGt0DxEq0;
301 } else {
302 debug_assert!(matches!(self.hess_degenerate, DegenType::NotYetDetermined));
303 if !self.get_deltas_for_wrong_inertia(
304 &mut delta_x,
305 &mut delta_s,
306 &mut delta_c,
307 &mut delta_d,
308 ip_data,
309 ) {
310 return None;
311 }
312 self.test_status = TrialStatus::DcEq0DxGt0;
313 }
314 }
315 TrialStatus::DcGt0DxEq0 => {
316 debug_assert!(self.delta_x_curr == 0.0 && self.delta_c_curr > 0.0);
317 debug_assert!(matches!(self.jac_degenerate, DegenType::NotYetDetermined));
318 if !self.perturb_always_cd {
319 self.delta_c_curr = 0.0;
320 self.delta_d_curr = 0.0;
321 if !self.get_deltas_for_wrong_inertia(
322 &mut delta_x,
323 &mut delta_s,
324 &mut delta_c,
325 &mut delta_d,
326 ip_data,
327 ) {
328 return None;
329 }
330 self.test_status = TrialStatus::DcEq0DxGt0;
331 } else if !self.get_deltas_for_wrong_inertia(
332 &mut delta_x,
333 &mut delta_s,
334 &mut delta_c,
335 &mut delta_d,
336 ip_data,
337 ) {
338 return None;
339 } else {
340 self.test_status = TrialStatus::DcGt0DxGt0;
341 }
342 }
343 TrialStatus::DcEq0DxGt0 => {
344 debug_assert!(self.delta_x_curr > 0.0 && self.delta_c_curr == 0.0);
345 let v = self.delta_cd(mu);
346 self.delta_c_curr = v;
347 self.delta_d_curr = v;
348 if !self.get_deltas_for_wrong_inertia(
349 &mut delta_x,
350 &mut delta_s,
351 &mut delta_c,
352 &mut delta_d,
353 ip_data,
354 ) {
355 return None;
356 }
357 self.test_status = TrialStatus::DcGt0DxGt0;
358 }
359 TrialStatus::DcGt0DxGt0 => {
360 if !self.get_deltas_for_wrong_inertia(
361 &mut delta_x,
362 &mut delta_s,
363 &mut delta_c,
364 &mut delta_d,
365 ip_data,
366 ) {
367 return None;
368 }
369 }
370 TrialStatus::NoTest => {
371 debug_assert!(false, "perturb_for_singular: NoTest in undetermined branch");
372 }
373 }
374 } else if self.delta_c_curr > 0.0 {
375 if !self.get_deltas_for_wrong_inertia(
377 &mut delta_x,
378 &mut delta_s,
379 &mut delta_c,
380 &mut delta_d,
381 ip_data,
382 ) {
383 return None;
384 }
385 } else {
386 let v = self.delta_cd(mu);
387 self.delta_c_curr = v;
388 self.delta_d_curr = v;
389 append_info(ip_data, "L");
390 }
391
392 let out = Deltas {
393 delta_x: self.delta_x_curr,
394 delta_s: self.delta_s_curr,
395 delta_c: self.delta_c_curr,
396 delta_d: self.delta_d_curr,
397 };
398 set_info_regu_x(ip_data, out.delta_x);
399 Some(out)
400 }
401
402 pub fn perturb_for_wrong_inertia(
405 &mut self,
406 mu: Number,
407 ip_data: Option<&dyn PerturbationSink>,
408 ) -> Option<Deltas> {
409 if std::env::var_os("POUNCE_DBG_PERT").is_some() {
410 if let Some(d) = ip_data {
411 d.debug(&format!(
412 "[PERT] iter={} WRONG_INERTIA mu={:.2e} dx_last={:.2e} dx_curr={:.2e}",
413 d.iter_count(),
414 mu,
415 self.delta_x_last,
416 self.delta_x_curr
417 ));
418 }
419 }
420 self.finalize_test(ip_data);
421
422 let mut delta_x = 0.0;
423 let mut delta_s = 0.0;
424 let mut delta_c = 0.0;
425 let mut delta_d = 0.0;
426 let mut ok = self.get_deltas_for_wrong_inertia(
427 &mut delta_x,
428 &mut delta_s,
429 &mut delta_c,
430 &mut delta_d,
431 ip_data,
432 );
433 if !ok && self.delta_c_curr == 0.0 {
440 debug_assert_eq!(self.delta_d_curr, 0.0);
441 let v = self.delta_cd(mu);
442 self.delta_c_curr = v;
443 self.delta_d_curr = v;
444 self.delta_x_curr = 0.0;
445 self.delta_s_curr = 0.0;
446 self.test_status = TrialStatus::NoTest;
447 if matches!(self.hess_degenerate, DegenType::Degenerate) {
448 self.hess_degenerate = DegenType::NotYetDetermined;
449 }
450 ok = self.get_deltas_for_wrong_inertia(
451 &mut delta_x,
452 &mut delta_s,
453 &mut delta_c,
454 &mut delta_d,
455 ip_data,
456 );
457 }
458 if !ok {
459 return None;
460 }
461 Some(Deltas {
462 delta_x,
463 delta_s,
464 delta_c,
465 delta_d,
466 })
467 }
468
469 pub fn current_perturbation(&self) -> Deltas {
471 Deltas {
472 delta_x: self.delta_x_curr,
473 delta_s: self.delta_s_curr,
474 delta_c: self.delta_c_curr,
475 delta_d: self.delta_d_curr,
476 }
477 }
478
479 fn get_deltas_for_wrong_inertia(
483 &mut self,
484 delta_x: &mut Number,
485 delta_s: &mut Number,
486 delta_c: &mut Number,
487 delta_d: &mut Number,
488 ip_data: Option<&dyn PerturbationSink>,
489 ) -> bool {
490 if self.delta_x_curr == 0.0 {
491 self.delta_x_curr = if self.delta_x_last == 0.0 {
492 self.delta_xs_init
493 } else {
494 self.delta_xs_min
495 .max(self.delta_x_last * self.delta_xs_dec_fact)
496 };
497 } else if self.delta_x_last == 0.0 || 1e5 * self.delta_x_last < self.delta_x_curr {
498 self.delta_x_curr *= self.delta_xs_first_inc_fact;
499 } else {
500 self.delta_x_curr *= self.delta_xs_inc_fact;
501 }
502 if self.delta_x_curr > self.delta_xs_max {
503 self.delta_x_last = 0.0;
504 self.delta_s_last = 0.0;
505 append_info(ip_data, "dx");
506 return false;
507 }
508 self.delta_s_curr = self.delta_x_curr;
509
510 *delta_x = self.delta_x_curr;
511 *delta_s = self.delta_s_curr;
512 *delta_c = self.delta_c_curr;
513 *delta_d = self.delta_d_curr;
514 set_info_regu_x(ip_data, *delta_x);
515 self.get_deltas_for_wrong_inertia_called = true;
516 true
517 }
518
519 fn delta_cd(&self, mu: Number) -> Number {
520 self.delta_cd_val * mu.powf(self.delta_cd_exp)
521 }
522
523 fn finalize_test(&mut self, ip_data: Option<&dyn PerturbationSink>) {
527 match self.test_status {
528 TrialStatus::NoTest => (),
529 TrialStatus::DcEq0DxEq0 => {
530 if matches!(self.hess_degenerate, DegenType::NotYetDetermined)
531 && matches!(self.jac_degenerate, DegenType::NotYetDetermined)
532 {
533 self.hess_degenerate = DegenType::NotDegenerate;
534 self.jac_degenerate = DegenType::NotDegenerate;
535 append_info(ip_data, "Nhj ");
536 } else if matches!(self.hess_degenerate, DegenType::NotYetDetermined) {
537 self.hess_degenerate = DegenType::NotDegenerate;
538 append_info(ip_data, "Nh ");
539 } else if matches!(self.jac_degenerate, DegenType::NotYetDetermined) {
540 self.jac_degenerate = DegenType::NotDegenerate;
541 append_info(ip_data, "Nj ");
542 }
543 }
544 TrialStatus::DcGt0DxEq0 => {
545 if matches!(self.hess_degenerate, DegenType::NotYetDetermined) {
546 self.hess_degenerate = DegenType::NotDegenerate;
547 append_info(ip_data, "Nh ");
548 }
549 if matches!(self.jac_degenerate, DegenType::NotYetDetermined) {
550 self.degen_iters += 1;
551 if self.degen_iters >= self.degen_iters_max {
552 self.jac_degenerate = DegenType::Degenerate;
553 append_info(ip_data, "Dj ");
554 }
555 append_info(ip_data, "L");
556 }
557 }
558 TrialStatus::DcEq0DxGt0 => {
559 if matches!(self.jac_degenerate, DegenType::NotYetDetermined) {
560 self.jac_degenerate = DegenType::NotDegenerate;
561 append_info(ip_data, "Nj ");
562 }
563 if matches!(self.hess_degenerate, DegenType::NotYetDetermined) {
564 self.degen_iters += 1;
565 if self.degen_iters >= self.degen_iters_max {
566 self.hess_degenerate = DegenType::Degenerate;
567 append_info(ip_data, "Dh ");
568 }
569 }
570 }
571 TrialStatus::DcGt0DxGt0 => {
572 self.degen_iters += 1;
573 if self.degen_iters >= self.degen_iters_max {
574 self.hess_degenerate = DegenType::Degenerate;
575 self.jac_degenerate = DegenType::Degenerate;
576 append_info(ip_data, "Dhj ");
577 }
578 append_info(ip_data, "L");
579 }
580 }
581 }
582}
583
584fn append_info(sink: Option<&dyn PerturbationSink>, s: &str) {
585 if let Some(h) = sink {
586 h.append_info(s);
587 }
588}
589
590fn set_info_regu_x(sink: Option<&dyn PerturbationSink>, v: Number) {
591 if let Some(h) = sink {
592 h.set_regu_x(v);
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599
600 #[test]
601 fn first_wrong_inertia_perturbation_is_delta_xs_init() {
602 let mut h = PdPerturbationHandler::new();
603 let d = h.perturb_for_wrong_inertia(0.1, None).unwrap();
604 assert!((d.delta_x - 1e-4).abs() < 1e-20);
606 assert_eq!(d.delta_x, d.delta_s);
607 assert_eq!(d.delta_c, 0.0);
608 assert_eq!(d.delta_d, 0.0);
609 }
610
611 #[test]
612 fn second_perturbation_uses_first_inc_fact() {
613 let mut h = PdPerturbationHandler::new();
617 let d1 = h.perturb_for_wrong_inertia(0.1, None).unwrap();
618 let d2 = h.perturb_for_wrong_inertia(0.1, None).unwrap();
619 assert!((d2.delta_x - d1.delta_x * 100.0).abs() < 1e-15);
620 }
621
622 #[test]
623 fn third_perturbation_uses_inc_fact() {
624 let mut h = PdPerturbationHandler::new();
627 h.delta_x_curr = 1e-2;
628 h.delta_x_last = 1e-2;
629 let d = h.perturb_for_wrong_inertia(0.1, None).unwrap();
630 assert!((d.delta_x - 1e-2 * 8.0).abs() < 1e-15);
631 }
632
633 #[test]
634 fn perturbation_caps_at_max_when_dcd_already_active() {
635 let mut h = PdPerturbationHandler::new();
640 h.delta_x_curr = h.delta_xs_max;
641 h.delta_c_curr = 1e-4;
642 h.delta_d_curr = 1e-4;
643 assert!(h.perturb_for_wrong_inertia(0.1, None).is_none());
644 }
645
646 #[test]
647 fn consider_new_system_with_perturb_always_cd_seeds_dcd() {
648 let mut h = PdPerturbationHandler::new();
649 h.set_perturb_always_cd(true);
650 let mu = 0.1;
651 let d = h.consider_new_system(mu, None).unwrap();
652 let expected = h.delta_cd_val * mu.powf(h.delta_cd_exp);
653 assert!((d.delta_c - expected).abs() < 1e-15);
654 assert!((d.delta_d - expected).abs() < 1e-15);
655 assert_eq!(d.delta_x, 0.0);
656 assert_eq!(d.delta_s, 0.0);
657 }
658
659 #[test]
660 fn consider_new_system_default_zeros_dcd() {
661 let mut h = PdPerturbationHandler::new();
662 let d = h.consider_new_system(0.1, None).unwrap();
663 assert_eq!(
664 d,
665 Deltas {
666 delta_x: 0.0,
667 delta_s: 0.0,
668 delta_c: 0.0,
669 delta_d: 0.0
670 }
671 );
672 }
673
674 #[test]
675 fn singular_in_test_dc_eq0_dx_eq0_seeds_dcd() {
676 let mut h = PdPerturbationHandler::new();
677 let _ = h.consider_new_system(0.1, None).unwrap();
678 assert_eq!(h.test_status, TrialStatus::DcEq0DxEq0);
682 let d = h.perturb_for_singular(0.1, None).unwrap();
683 let expected = h.delta_cd_val * (0.1_f64).powf(h.delta_cd_exp);
684 assert!((d.delta_c - expected).abs() < 1e-15);
685 assert!((d.delta_d - expected).abs() < 1e-15);
686 assert_eq!(d.delta_x, 0.0);
687 assert_eq!(h.test_status, TrialStatus::DcGt0DxEq0);
688 }
689
690 #[test]
691 fn singular_when_determined_with_dc_zero_seeds_dcd() {
692 let mut h = PdPerturbationHandler::new();
693 h.hess_degenerate = DegenType::NotDegenerate;
694 h.jac_degenerate = DegenType::NotDegenerate;
695 h.test_status = TrialStatus::NoTest;
696 let d = h.perturb_for_singular(0.1, None).unwrap();
697 let expected = h.delta_cd_val * (0.1_f64).powf(h.delta_cd_exp);
698 assert!((d.delta_c - expected).abs() < 1e-15);
699 }
700
701 #[test]
712 fn a_second_singular_verdict_from_the_ladder_does_not_trip_the_probe() {
713 let mut h = PdPerturbationHandler::new();
714 let _ = h.consider_new_system(0.1, None).unwrap();
715 let _ = h.perturb_for_singular(0.1, None).unwrap();
718 assert_eq!(h.test_status, TrialStatus::DcGt0DxEq0);
719 assert_eq!(h.jac_degenerate, DegenType::NotYetDetermined);
720 let d = h.perturb_for_wrong_inertia(0.1, None).unwrap();
724 assert!(d.delta_x > 0.0);
725 assert_eq!(h.jac_degenerate, DegenType::NotYetDetermined);
726 assert_eq!(h.test_status, TrialStatus::DcGt0DxEq0);
727 let d = h
730 .perturb_for_singular(0.1, None)
731 .expect("a singular verdict from the ladder must not be fatal");
732 assert!(
733 d.delta_c > 0.0,
734 "δ_c was dropped by the abandoned probe: {}",
735 d.delta_c,
736 );
737 assert_eq!(
738 h.test_status,
739 TrialStatus::NoTest,
740 "a probe whose precondition no longer holds must be abandoned",
741 );
742 }
743
744 #[test]
748 fn singular_after_a_delta_x_step_abandons_the_probe() {
749 let mut h = PdPerturbationHandler::new();
750 let _ = h.consider_new_system(0.1, None).unwrap();
753 assert_eq!(h.test_status, TrialStatus::DcEq0DxEq0);
754 let d = h.perturb_for_wrong_inertia(0.1, None).unwrap();
757 assert!(d.delta_x > 0.0);
758 let d = h.perturb_for_singular(0.1, None).unwrap();
760 let expected_cd = h.delta_cd_val * (0.1_f64).powf(h.delta_cd_exp);
761 assert!(
762 (d.delta_c - expected_cd).abs() < 1e-20,
763 "δ_c was not raised: {}",
764 d.delta_c,
765 );
766 assert_eq!(d.delta_c, d.delta_d);
767 assert_eq!(
768 d.delta_x, h.delta_xs_init,
769 "the δ_x ladder lost the rung it had already paid for",
770 );
771 assert_eq!(
772 h.test_status,
773 TrialStatus::NoTest,
774 "a probe whose precondition no longer holds must be abandoned, \
775 not carried into finalize_test",
776 );
777 }
778
779 #[test]
780 fn finalize_test_sets_not_degenerate_after_dc_eq0_dx_eq0_pass() {
781 let mut h = PdPerturbationHandler::new();
782 let _ = h.consider_new_system(0.1, None).unwrap();
783 let _ = h.consider_new_system(0.1, None).unwrap();
787 assert_eq!(h.hess_degenerate, DegenType::NotDegenerate);
788 assert_eq!(h.jac_degenerate, DegenType::NotDegenerate);
789 }
790
791 #[test]
792 fn current_perturbation_returns_committed_values() {
793 let mut h = PdPerturbationHandler::new();
794 h.delta_x_curr = 1.0;
795 h.delta_s_curr = 2.0;
796 h.delta_c_curr = 3.0;
797 h.delta_d_curr = 4.0;
798 let d = h.current_perturbation();
799 assert_eq!(d.delta_x, 1.0);
800 assert_eq!(d.delta_s, 2.0);
801 assert_eq!(d.delta_c, 3.0);
802 assert_eq!(d.delta_d, 4.0);
803 }
804}