1use bincode_trait_derive::{Decode, Encode};
2use eyre::Result;
3use itertools::Itertools;
4use linnet::half_edge::involution::EdgeVec;
5use spenso::algebra::complex::Complex;
6use symbolica::{
7 atom::{Atom, AtomCore, Symbol},
8 domains::{
9 dual::{DualNumberStructure, HyperDual},
10 float::Real,
11 },
12 evaluate::{FunctionMap, OptimizationSettings},
13 function, parse,
14 poly::PolyVariable,
15 symbol,
16};
17use tracing::debug;
18use typed_index_collections::TiVec;
19
20use crate::{
21 GammaLoopContext,
22 cff::esurface::Esurface,
23 graph::{LmbIndex, LoopMomentumBasis},
24 integrands::{
25 evaluation::EvaluationMetaData,
26 process::{GenericEvaluator, evaluators::evaluate_evaluator},
27 },
28 momentum::{
29 Energy, FourMomentum,
30 sample::{MomentumSample, SubspaceData},
31 },
32 processes::EvaluatorSettings,
33 settings::runtime::{
34 IntegratedCounterTermRange, IntegratedCounterTermSettings, UVLocalisationSettings,
35 },
36 utils::{
37 self, F, FloatLike, GS,
38 hyperdual_utils::{DualOrNot, new_constant, simple_n_deriv_shape},
39 },
40};
41
42pub mod amplitude_counterterm;
43pub mod lu_counterterm;
44pub mod overlap;
45pub mod overlap_subspace;
46
47fn evaluate_uv_damper<T: FloatLike>(
48 radius: &F<T>,
49 radius_star: &F<T>,
50 e_cm: &F<T>,
51 settings: &UVLocalisationSettings,
52) -> F<T> {
53 if settings.force_uv_dampers_to_one {
54 return radius.one();
55 }
56
57 let normalizing_scale = match settings.dynamic_width {
58 true => radius_star,
59 false => e_cm,
60 };
61
62 let delta_r = radius - radius_star;
63 let sliver_width = F::from_f64(settings.sliver_width) * normalizing_scale;
64
65 if delta_r.abs() > sliver_width || (settings.smooth_sliver && delta_r.abs() == sliver_width) {
66 return radius.zero();
67 }
68
69 let delta_r_sq = &delta_r * &delta_r;
70 let width = F::from_f64(settings.gaussian_width) * normalizing_scale;
71 let width_sq = &width * &width;
72
73 let mut exponent = -&delta_r_sq / width_sq;
74 if settings.smooth_sliver {
75 let gap = sliver_width.square() - &delta_r_sq;
77 exponent -= delta_r_sq / gap;
78 }
79 exponent.exp()
80}
81
82fn evaluate_uv_damper_dual<T: FloatLike>(
83 radius: &HyperDual<F<T>>,
84 radius_star: &HyperDual<F<T>>,
85 e_cm: &F<T>,
86 settings: &UVLocalisationSettings,
87) -> HyperDual<F<T>> {
88 if settings.force_uv_dampers_to_one {
89 return new_constant(radius, &radius.values[0].one());
90 }
91
92 let normalizing_scale = if settings.dynamic_width {
93 radius_star.clone()
94 } else {
95 new_constant(radius, e_cm)
96 };
97
98 let delta_r = radius.clone() - radius_star.clone();
99 let sliver_width =
100 new_constant(radius, &F::from_f64(settings.sliver_width)) * normalizing_scale.clone();
101
102 if delta_r.values[0].abs() > sliver_width.values[0]
103 || (settings.smooth_sliver && delta_r.values[0].abs() == sliver_width.values[0])
104 {
105 return new_constant(radius, &radius.values[0].zero());
106 }
107
108 let delta_r_sq = delta_r.clone() * delta_r;
109 let width = new_constant(radius, &F::from_f64(settings.gaussian_width)) * normalizing_scale;
110 let width_sq = width.clone() * width;
111
112 let mut exponent = -delta_r_sq.clone() / width_sq;
113 if settings.smooth_sliver {
114 let gap = sliver_width.clone() * sliver_width - delta_r_sq.clone();
115 exponent -= delta_r_sq / gap;
116 }
117 exponent.exp()
118}
119
120fn evaluate_integrated_ct_normalisation<T: FloatLike>(
121 radius: &F<T>,
122 radius_star: &F<T>,
123 _e_cm: &F<T>,
124 settings: &IntegratedCounterTermSettings,
125) -> F<T> {
126 match &settings.range {
127 IntegratedCounterTermRange::Infinite {
128 h_function_settings,
129 } => {
130 let h = utils::h(&(radius / radius_star), None, None, h_function_settings);
133 h * (radius_star).inv()
134 }
135 IntegratedCounterTermRange::Compact {} => {
136 todo!();
137 }
138 }
139}
140
141fn evaluate_integrated_ct_normalisation_dual<T: FloatLike>(
142 radius: &HyperDual<F<T>>,
143 radius_star: &HyperDual<F<T>>,
144 _e_cm: &F<T>,
145 settings: &IntegratedCounterTermSettings,
146) -> HyperDual<F<T>> {
147 match &settings.range {
148 IntegratedCounterTermRange::Infinite {
149 h_function_settings,
150 } => {
151 let h = utils::h_dual(
152 &(radius.clone() / radius_star.clone()),
153 None,
154 None,
155 h_function_settings,
156 );
157 h / radius_star.clone()
158 }
159 IntegratedCounterTermRange::Compact {} => {
160 todo!();
161 }
162 }
163}
164
165#[derive(Clone, Encode, Decode)]
166#[trait_decode(trait = GammaLoopContext)]
167pub(crate) struct RstarTDependenceEvaluator {
168 dual_shape_for_esurface_evaluation: Vec<Vec<usize>>,
169 implicit_function_theorem: Option<GenericEvaluator>,
170}
171
172impl RstarTDependenceEvaluator {
173 pub(crate) fn supports_t_derivatives(&self) -> bool {
174 self.implicit_function_theorem.is_some()
175 }
176
177 fn evaluate_alpha<T: FloatLike>(
178 &mut self,
179 input: RstarTDependenceInput<'_, T>,
180 evaluation_metadata: &mut EvaluationMetaData,
181 ) -> HyperDual<F<T>> {
182 let RstarTDependenceInput {
183 t_star,
184 alpha,
185 overlap_center,
186 subspace,
187 unrescaled_momentum_sample,
188 representative_sample,
189 masses,
190 threshold_esurface,
191 lmb,
192 all_lmbs,
193 } = input;
194 debug!("t-star: {}", t_star);
195 debug!("projection alpha: {}", alpha);
196
197 let dual = HyperDual::new(self.dual_shape_for_esurface_evaluation.clone());
198 let dual_alpha = dual.variable(0, alpha.clone());
199 let dual_t = dual.variable(1, t_star.clone());
200
201 let rescale_tstar = unrescaled_momentum_sample
202 .loop_moms()
203 .rescale_with_hyper_dual(&dual_t, None);
204
205 let dualized_externals_three_momenta = unrescaled_momentum_sample
206 .external_moms()
207 .iter()
208 .map(|fm: &FourMomentum<F<T>>| fm.spatial.map_ref(&|x| new_constant(&dual, x)))
209 .collect();
210
211 let mut lmb_transform = rescale_tstar.lmb_transform(
212 lmb,
213 subspace.get_lmb(all_lmbs),
214 &dualized_externals_three_momenta,
215 );
216
217 for (momentum, base) in lmb_transform
221 .0
222 .iter_mut()
223 .zip(representative_sample.loop_moms().iter())
224 {
225 momentum.px.values[0] = base.px.clone();
226 momentum.py.values[0] = base.py.clone();
227 momentum.pz.values[0] = base.pz.clone();
228 }
229
230 let dualized_overlap_center = overlap_center
231 .iter()
232 .map(|momentum| momentum.map_ref(&|x| new_constant(&dual, x)))
233 .collect::<crate::momentum::sample::LoopMomenta<_>>();
234
235 let shifted_t_dependent_momenta = lmb_transform
236 .iter()
237 .zip(dualized_overlap_center.iter())
238 .map(|(momentum, center)| momentum.clone() - center.clone())
239 .collect::<crate::momentum::sample::LoopMomenta<_>>();
240
241 let dualized_external_fourmomenta = dualized_externals_three_momenta
242 .into_iter()
243 .zip(unrescaled_momentum_sample.external_moms())
244 .map(|(spatial, fm)| FourMomentum {
245 spatial,
246 temporal: Energy {
247 value: new_constant(&dual, &fm.temporal.value),
248 },
249 })
250 .collect();
251
252 let rescale_rstar = shifted_t_dependent_momenta
255 .rescale(&dual_alpha, subspace.as_subspace_simple())
256 .iter()
257 .zip(dualized_overlap_center.iter())
258 .map(|(momentum, center)| momentum.clone() + center.clone())
259 .collect::<crate::momentum::sample::LoopMomenta<_>>();
260
261 let dual_esurface = threshold_esurface.compute_from_dual_momenta(
262 subspace.get_lmb(all_lmbs),
263 masses,
264 &rescale_rstar,
265 &dualized_external_fourmomenta,
266 );
267
268 debug!("Dual e-surface: {}", dual_esurface);
269
270 let params = dual_esurface.values[1..]
273 .iter()
274 .zip(dual_esurface.get_shape().into_iter().skip(1))
275 .map(|(coefficient, orders)| {
276 let derivative = orders
277 .iter()
278 .flat_map(|order| 2..=*order)
279 .fold(coefficient.clone(), |value, factor| {
280 value * coefficient.from_usize(factor)
281 });
282 Complex::new_re(derivative)
283 })
284 .collect_vec();
285
286 debug!("Parameters for implicit function theorem: {:#?}", params);
287
288 let result = evaluate_evaluator(
289 self.implicit_function_theorem
290 .as_mut()
291 .expect("alpha(t) evaluator requested without t-derivative support"),
292 ¶ms,
293 evaluation_metadata,
294 )
295 .into_iter()
296 .map(DualOrNot::unwrap_real)
297 .collect_vec();
298
299 debug!("Result from implicit function theorem: {:#?}", result);
300
301 let mut dual_values = vec![alpha.clone()];
302 let mut n_factorial = alpha.one();
303 for (i, result) in result.into_iter().enumerate() {
304 n_factorial *= alpha.from_usize(i + 1);
305 dual_values.push(result.re / &n_factorial);
306 }
307
308 HyperDual::from_values(simple_n_deriv_shape(dual_values.len() - 1), dual_values)
309 }
310}
311
312pub(crate) struct RstarTDependenceInput<'a, T: FloatLike> {
313 pub t_star: &'a F<T>,
314 pub alpha: &'a F<T>,
315 pub overlap_center: &'a crate::momentum::sample::LoopMomenta<F<T>>,
316 pub subspace: &'a SubspaceData,
317 pub unrescaled_momentum_sample: &'a MomentumSample<T>,
318 pub representative_sample: &'a MomentumSample<T>,
319 pub masses: &'a EdgeVec<F<T>>,
320 pub threshold_esurface: &'a Esurface,
321 pub lmb: &'a LoopMomentumBasis,
322 pub all_lmbs: &'a TiVec<LmbIndex, LoopMomentumBasis>,
323}
324
325pub(crate) fn generate_rstar_t_dependence_evaluator(
329 num_t_derivatives: usize,
330) -> Result<RstarTDependenceEvaluator> {
331 if num_t_derivatives == 0 {
332 return Ok(RstarTDependenceEvaluator {
333 dual_shape_for_esurface_evaluation: Vec::new(),
334 implicit_function_theorem: None,
335 });
336 }
337
338 let t = symbol!("t");
339
340 let rstar = parse!("r_star(t)");
341 let e_surface = function!(GS.eta, rstar.clone(), Atom::var(t));
342
343 let mut rstar_derivatives = vec![];
344 for i in 0..num_t_derivatives {
345 if i == 0 {
346 rstar_derivatives.push(rstar.derivative(t));
347 } else {
348 rstar_derivatives.push(rstar_derivatives.last().unwrap().derivative(t));
349 }
350 }
351
352 let mut equations = vec![];
353 for i in 0..num_t_derivatives {
354 if i == 0 {
355 equations.push(e_surface.derivative(t));
356 } else {
357 equations.push(equations.last().unwrap().derivative(t));
358 }
359 }
360
361 let mut solutions = equations
362 .iter()
363 .zip(&rstar_derivatives)
364 .map(|(eq, variable)| {
365 let solutions = Atom::solve(std::slice::from_ref(eq))
366 .wrt_with_exponent::<u8, _>(std::slice::from_ref(variable))
367 .unwrap();
368 let [solution] = solutions.iter().as_slice() else {
369 panic!(
370 "expected one implicit-function solution, got {} branches",
371 solutions.len()
372 );
373 };
374 assert!(
375 solution.free_variables().is_empty(),
376 "implicit-function solution is underdetermined"
377 );
378 let variable = PolyVariable::try_from(variable.clone()).unwrap();
379 solution.get(&variable).cloned().unwrap()
380 })
381 .collect_vec();
382
383 for i in 1..solutions.len() {
384 for j in 0..i {
385 solutions[i] = solutions[i]
386 .replace(rstar_derivatives[j].clone())
387 .with(solutions[j].clone());
388 }
389 }
390
391 let mut dual_shape = vec![vec![0, 0]];
393 let mut params = vec![];
394 for i in 1..=num_t_derivatives {
395 let mut eta_derivatives_at_this_order = vec![];
396
397 let mut current_r_derivative_counter = 0;
398 let mut current_t_derivative_counter = i;
399
400 loop {
401 dual_shape.push(vec![
402 current_r_derivative_counter,
403 current_t_derivative_counter,
404 ]);
405 let eta_derivative = function!(
406 Symbol::DERIVATIVE,
407 current_r_derivative_counter,
408 current_t_derivative_counter,
409 GS.eta,
410 rstar.clone(),
411 Atom::var(t)
412 );
413
414 eta_derivatives_at_this_order.push(eta_derivative);
415
416 if current_t_derivative_counter == 0 {
417 break;
418 }
419 current_r_derivative_counter += 1;
420 current_t_derivative_counter -= 1;
421 }
422
423 params.extend(eta_derivatives_at_this_order);
424 }
425
426 let fn_map = FunctionMap::new();
427 let fn_map_entries = vec![];
428 let implict_function_theorem = GenericEvaluator::new_from_raw_params(
429 solutions,
430 ¶ms,
431 &fn_map,
432 fn_map_entries,
433 OptimizationSettings::default(),
434 None,
435 &EvaluatorSettings::default(),
436 )?;
437
438 Ok(RstarTDependenceEvaluator {
439 dual_shape_for_esurface_evaluation: dual_shape,
440 implicit_function_theorem: Some(implict_function_theorem),
441 })
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::{
448 integrands::process::GenericEvaluatorFloat,
449 processes::cross_section::CrossSectionGraph,
450 settings::runtime::{HFunction, HFunctionSettings},
451 utils::hyperdual_utils::new_from_values,
452 };
453
454 #[test]
455 fn integrated_ct_profiles_preserve_the_radial_residue() {
456 for (function, power) in [
457 (HFunction::Exponential, None),
458 (HFunction::PolyExponential, None),
459 (HFunction::PolyExponential, Some(4)),
460 (HFunction::PolyExponential, Some(16)),
461 (HFunction::PolyLeftRightExponential, None),
465 ] {
466 for sigma in [0.5, 2.0] {
467 let settings = IntegratedCounterTermSettings {
468 range: IntegratedCounterTermRange::Infinite {
469 h_function_settings: HFunctionSettings {
470 function: function.clone(),
471 sigma,
472 power,
473 ..Default::default()
474 },
475 },
476 };
477 for radius_star in [0.3, 3.0] {
478 let tail_multiple = if function == HFunction::PolyLeftRightExponential {
484 64.0
485 } else {
486 8.0
487 };
488 let step = tail_multiple * sigma * radius_star / 2048.0;
489 let integral: f64 = (0..2048)
490 .map(|i| {
491 evaluate_integrated_ct_normalisation(
492 &F((i as f64 + 0.5) * step),
493 &F(radius_star),
494 &F(1.0),
495 &settings,
496 )
497 .0
498 })
499 .sum::<f64>()
500 * step;
501 let tolerance = if function == HFunction::PolyLeftRightExponential {
502 1.0e-8
503 } else {
504 2.0e-12
505 };
506 assert!(
507 (integral - 1.0).abs() < tolerance,
508 "{function:?}, power={power:?}, sigma={sigma}, r_star={radius_star}: {integral}"
509 );
510 }
511 }
512 }
513 }
514
515 #[test]
516 fn integrated_ct_profile_dual_matches_normalized_density_derivatives() {
517 let sigma = 0.6_f64;
518 let radius_star = 1.7_f64;
519 let settings = IntegratedCounterTermSettings {
520 range: IntegratedCounterTermRange::Infinite {
521 h_function_settings: HFunctionSettings {
522 function: HFunction::Exponential,
523 sigma,
524 ..Default::default()
525 },
526 },
527 };
528 let shape = HyperDual::new(simple_n_deriv_shape(2));
529 let dual_star = new_from_values(&shape, &[F(radius_star), F(1.0), F(0.0)]);
530 for radius in [0.2_f64, 1.4, 4.0] {
531 let dual_radius = new_from_values(&shape, &[F(radius), F(0.0), F(0.0)]);
532 let actual = evaluate_integrated_ct_normalisation_dual(
533 &dual_radius,
534 &dual_star,
535 &F(1.0),
536 &settings,
537 );
538 let x = radius / (sigma * radius_star);
540 let density =
541 2.0 * (-x * x).exp() / (std::f64::consts::PI.sqrt() * sigma * radius_star);
542 let log_derivative = (2.0 * x * x - 1.0) / radius_star;
543 let second_log_derivative = (1.0 - 6.0 * x * x) / radius_star.powi(2);
544 let expected = [
545 density,
546 density * log_derivative,
547 density * (log_derivative.powi(2) + second_log_derivative) / 2.0,
548 ];
549 for (actual, expected) in actual.values.iter().zip(expected) {
550 assert!((actual.0 - expected).abs() < 2.0e-13 * expected.abs().max(1.0));
551 }
552 }
553 }
554
555 #[test]
556 fn uv_smooth_sliver_preserves_pole_symmetry_and_hard_mode() {
557 for dynamic_width in [false, true] {
558 let settings = UVLocalisationSettings {
559 smooth_sliver: true,
560 sliver_width: 0.5,
561 dynamic_width,
562 ..Default::default()
563 };
564 let radius_star = F(2.0_f64);
565 let e_cm = F(4.0_f64);
566 let half_width = if dynamic_width { 1.0 } else { 2.0 };
567 assert_eq!(
568 evaluate_uv_damper(&radius_star, &radius_star, &e_cm, &settings),
569 F(1.0)
570 );
571 for fraction in [0.25, 0.75] {
572 let delta = F(fraction * half_width);
573 assert_eq!(
574 evaluate_uv_damper(&(radius_star + delta), &radius_star, &e_cm, &settings),
575 evaluate_uv_damper(&(radius_star - delta), &radius_star, &e_cm, &settings)
576 );
577 }
578 let hard = UVLocalisationSettings {
579 smooth_sliver: false,
580 ..settings.clone()
581 };
582 for sign in [-1.0, 1.0] {
583 let edge = F(radius_star.0 + sign * half_width);
584 assert_eq!(
585 evaluate_uv_damper(&edge, &radius_star, &e_cm, &settings),
586 F(0.0)
587 );
588 assert_eq!(
590 evaluate_uv_damper(&edge, &radius_star, &e_cm, &hard),
591 F((-0.25_f64).exp())
592 );
593 }
594 let forced = UVLocalisationSettings {
595 force_uv_dampers_to_one: true,
596 ..settings
597 };
598 assert_eq!(
599 evaluate_uv_damper(&F(100.0), &radius_star, &e_cm, &forced),
600 F(1.0)
601 );
602 }
603 }
604
605 #[test]
606 fn uv_smooth_sliver_dual_matches_derivatives_and_flat_boundary() {
607 let shape = HyperDual::new(simple_n_deriv_shape(2));
608 let radius_star = new_from_values(&shape, &[F(2.0_f64), F(0.7), F(0.0)]);
609 for dynamic_width in [false, true] {
610 let settings = UVLocalisationSettings {
611 smooth_sliver: true,
612 sliver_width: 0.5,
613 dynamic_width,
614 ..Default::default()
615 };
616 let scale = if dynamic_width { 2.0 } else { 4.0 };
617 let scale_derivative = if dynamic_width { 0.7 } else { 0.0 };
618 for u in [-0.4_f64, 0.0, 0.125] {
619 let radius = new_from_values(&shape, &[F(2.0 + u * scale), F(0.3), F(0.0)]);
620 let actual = evaluate_uv_damper_dual(&radius, &radius_star, &F(4.0), &settings);
621 let gap = 0.25 - u * u;
623 let value = (-u * u - u * u / gap).exp();
624 let log_first = -2.0 * u - 0.5 * u / gap.powi(2);
625 let log_second = -2.0 - 0.5 / gap.powi(2) - 2.0 * u * u / gap.powi(3);
626 let u_first = (-0.4 - u * scale_derivative) / scale;
627 let u_second = -2.0 * scale_derivative * u_first / scale;
628 let expected = [
629 value,
630 value * log_first * u_first,
631 value
632 * ((log_first.powi(2) + log_second) * u_first.powi(2)
633 + log_first * u_second)
634 / 2.0,
635 ];
636 let scalar = evaluate_uv_damper(
637 &radius.values[0],
638 &radius_star.values[0],
639 &F(4.0),
640 &settings,
641 );
642 assert!((actual.values[0].0 - scalar.0).abs() < 2.0e-14);
643 for (actual, expected) in actual.values.iter().zip(expected) {
644 assert!((actual.0 - expected).abs() < 2.0e-13 * expected.abs().max(1.0));
645 }
646 }
647 for u in [-0.6_f64, -0.5, -0.4995, 0.4995, 0.5, 0.6] {
648 let radius = new_from_values(&shape, &[F(2.0 + u * scale), F(0.3), F(0.0)]);
649 let actual = evaluate_uv_damper_dual(&radius, &radius_star, &F(4.0), &settings);
650 for coefficient in actual.values {
651 assert!(coefficient.0.abs() < 1.0e-190);
652 if u.abs() >= 0.5 {
653 assert_eq!(coefficient, F(0.0));
654 }
655 }
656 }
657 let forced = UVLocalisationSettings {
658 force_uv_dampers_to_one: true,
659 ..settings
660 };
661 let radius = new_from_values(&shape, &[F(100.0), F(0.3), F(0.0)]);
662 assert_eq!(
663 evaluate_uv_damper_dual(&radius, &radius_star, &F(4.0), &forced).values,
664 vec![F(1.0), F(0.0), F(0.0)]
665 );
666 }
667 }
668
669 #[test]
670 fn uv_smooth_sliver_helper_preserves_signed_radial_principal_value() {
671 crate::initialisation::test_initialise().unwrap();
672 let settings = UVLocalisationSettings {
673 smooth_sliver: true,
674 sliver_width: 1.5,
675 ..Default::default()
676 };
677 for loop_count in [1, 2] {
678 let (pieces, _) =
679 CrossSectionGraph::single_th_prefactor_helper_atoms(1, loop_count, false, false);
680 let mut helper = GenericEvaluator::new_from_raw_params(
681 [pieces.local],
682 &CrossSectionGraph::single_th_prefactor_helper_params(1, false),
683 &FunctionMap::new(),
684 vec![],
685 OptimizationSettings::default(),
686 None,
687 &EvaluatorSettings::default(),
688 )
689 .unwrap()
690 .into_eager_only();
691 for radius_star in [0.5, 2.0] {
692 let step = 1.0 / 2048.0;
695 let steps = ((radius_star + settings.sliver_width) / step) as usize;
696 let mut integral = 0.0;
697 for i in 0..steps {
698 let radius = F((i as f64 + 0.5) * step);
699 let plus = evaluate_uv_damper(&radius, &F(radius_star), &F(1.0), &settings);
700 let minus = evaluate_uv_damper(&(-radius), &F(radius_star), &F(1.0), &settings);
701 let params = [F(1.0), F(1.0), radius, F(radius_star), plus, minus, F(0.0)]
702 .map(Complex::new_re);
703 let value = f64::get_evaluator_single(&mut helper)(¶ms);
704 integral += value.re.0 * radius.0.powi(3 * loop_count as i32 - 1) * step;
705 }
706 assert!(
707 integral.abs() < 1.0e-10,
708 "{loop_count} loops, r_star={radius_star}: PV={integral}"
709 );
710 }
711 }
712 }
713
714 #[test]
715 fn uv_damper_can_be_forced_to_one() {
716 let settings = UVLocalisationSettings {
717 force_uv_dampers_to_one: true,
718 ..Default::default()
719 };
720
721 assert_eq!(
722 evaluate_uv_damper(&F(100.0_f64), &F(1.0_f64), &F(1.0_f64), &settings),
723 F(1.0_f64)
724 );
725 }
726
727 #[test]
728 fn uv_damper_dual_can_be_forced_to_constant_one() {
729 let settings = UVLocalisationSettings {
730 force_uv_dampers_to_one: true,
731 ..Default::default()
732 };
733 let shape = HyperDual::new(simple_n_deriv_shape(1));
734 let radius = new_from_values(&shape, &[F(100.0_f64), F(5.0_f64)]);
735 let radius_star = new_from_values(&shape, &[F(1.0_f64), F(3.0_f64)]);
736
737 let damper = evaluate_uv_damper_dual(&radius, &radius_star, &F(1.0_f64), &settings);
738
739 assert_eq!(damper.values, vec![F(1.0_f64), F(0.0_f64)]);
740 }
741
742 #[test]
743 fn test_rstar_t_dependence_evaluator() {
744 generate_rstar_t_dependence_evaluator(3).unwrap();
745 }
746
747 #[test]
748 fn test_rstar_t_dependence_evaluator_zero_derivatives() {
749 let evaluator = generate_rstar_t_dependence_evaluator(0).unwrap();
750 assert!(!evaluator.supports_t_derivatives());
751 }
752}