Skip to main content

quad_rs/
config.rs

1use num_traits::{Float, FromPrimitive};
2
3use crate::{
4    ComplexScalar, Contour, ErrorNorm, IndentSide,
5    core::{GaussKronrodConfig, SingularityHandling},
6};
7
8#[derive(Clone, Debug)]
9pub struct ContourDeformation<F>
10where
11    F: ComplexScalar,
12{
13    /// Singularities to indent around.
14    pub indentations: Vec<Indentation<F>>,
15
16    /// Distance used when determining whether a singularity lies on a contour
17    /// segment.
18    pub tolerance: F,
19}
20
21#[derive(Clone, Debug)]
22pub struct Indentation<F>
23where
24    F: ComplexScalar,
25{
26    /// Location of the singularity.
27    pub point: F::Complex,
28
29    /// Radius of the indentation.
30    pub radius: F,
31
32    /// Side on which the contour should pass.
33    pub side: IndentSide,
34}
35
36/// Configuration for an adaptive integration run.
37///
38/// `IntegratorConfig` contains user-facing options that control convergence,
39/// quadrature order, diagnostic storage, error reduction, and singularity
40/// handling.
41///
42/// The configuration is intentionally separate from the integrator state. The
43/// config describes how the algorithm should run; the state records what has
44/// happened during a particular run.
45#[derive(Clone, Debug)]
46pub struct IntegratorConfig<F: ComplexScalar> {
47    /// Whether to store quadrature samples in each returned segment.
48    ///
49    /// Enabling this is useful for diagnostics and visualization, but increases
50    /// memory usage.
51    pub(crate) store_segment_data: bool,
52
53    /// Embedded Gauss rule order.
54    ///
55    /// The corresponding Gauss–Kronrod rule has order `2 * integrator_order + 1`.
56    /// The default value `10` gives the common 10/21 Gauss–Kronrod pair.
57    pub(crate) integrator_order: usize,
58
59    /// Minimum allowed segment width before subdivision stops.
60    ///
61    /// This prevents infinite subdivision near singularities or discontinuities.
62    pub(crate) minimum_segment_width: F,
63
64    /// Method used to reduce vector- or matrix-valued local errors to a scalar.
65    pub(crate) error_norm: ErrorNorm,
66
67    /// Policy used when the integrand evaluates to a non-finite value.
68    pub(crate) singularity_handling: SingularityHandling,
69
70    /// Target relative tolerance.
71    pub(crate) relative_tolerance: F,
72
73    /// Target absolute tolerance.
74    pub(crate) absolute_tolerance: F,
75
76    /// Maximum permitted function evaluations
77    pub(crate) max_function_evaluations: usize,
78
79    /// Number of consecutive tolerance checks required before termination.
80    ///
81    /// A value greater than one can make termination less sensitive to transient
82    /// fluctuations in the adaptive error estimate.
83    pub(crate) tolerance_window: usize,
84
85    pub(crate) contour_deformation: Option<ContourDeformation<F>>,
86}
87
88impl<F> Default for IntegratorConfig<F>
89where
90    F: Float + FromPrimitive + ComplexScalar,
91{
92    fn default() -> Self {
93        Self {
94            store_segment_data: false,
95            integrator_order: 10,
96            minimum_segment_width: F::from_f64(1e-12).unwrap(),
97            error_norm: ErrorNorm::Max,
98            singularity_handling: SingularityHandling::RecursiveSplit { max_depth: 32 },
99            relative_tolerance: F::from_f64(1.49e-8).unwrap(),
100            absolute_tolerance: F::from_f64(1.49e-8).unwrap(),
101            max_function_evaluations: 5000,
102            tolerance_window: 10,
103            contour_deformation: None,
104        }
105    }
106}
107
108impl<F> IntegratorConfig<F>
109where
110    F: Float + FromPrimitive + ComplexScalar,
111{
112    /// Create a new default config
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    pub(crate) fn deform_contour(&self, mut contour: Contour<F>) -> Contour<F> {
118        if let Some(deformation) = &self.contour_deformation {
119            for indentation in &deformation.indentations {
120                contour = contour.indent(
121                    indentation.point,
122                    indentation.radius,
123                    indentation.side,
124                    deformation.tolerance,
125                );
126            }
127        }
128
129        contour
130    }
131
132    /// Add an indentation to the contour at the given point
133    pub fn with_indentation(mut self, point: F::Complex, radius: F, side: IndentSide) -> Self {
134        let deformation = self
135            .contour_deformation
136            .get_or_insert_with(|| ContourDeformation {
137                indentations: Vec::new(),
138                tolerance: self.minimum_segment_width,
139            });
140
141        deformation.indentations.push(Indentation {
142            point,
143            radius,
144            side,
145        });
146
147        self
148    }
149
150    /// Set the tolerance for deformation.
151    ///
152    /// Points within tolerance of the contour will be deformed around
153    pub fn with_deformation_tolerance(mut self, tolerance: F) -> Self {
154        let deformation = self
155            .contour_deformation
156            .get_or_insert_with(|| ContourDeformation {
157                indentations: Vec::new(),
158                tolerance,
159            });
160
161        deformation.tolerance = tolerance;
162        self
163    }
164
165    /// Whether to retain samples
166    pub fn store_segment_data(mut self) -> Self {
167        self.store_segment_data = true;
168        self
169    }
170
171    /// Set the quadrature order for integration
172    pub fn with_integrator_order(mut self, order: usize) -> Self {
173        self.integrator_order = order;
174        self
175    }
176
177    /// Set the minimum permitted segment width
178    pub fn with_minimum_segment_width(mut self, width: F) -> Self {
179        self.minimum_segment_width = width;
180        self
181    }
182
183    /// Set the method for error norm reduction for non-scalar outputs
184    pub fn with_error_norm(mut self, norm: ErrorNorm) -> Self {
185        self.error_norm = norm;
186        self
187    }
188
189    /// Set the maximum number of function evaluations permitted
190    pub fn with_max_function_evalutions(mut self, max_function_evaluations: usize) -> Self {
191        self.max_function_evaluations = max_function_evaluations;
192        self
193    }
194
195    /// Set the method of singularity handling
196    pub fn with_singularity_handling(mut self, handling: SingularityHandling) -> Self {
197        self.singularity_handling = handling;
198        self
199    }
200
201    /// Set the relative tolerance at convergence
202    pub fn with_relative_tolerance(mut self, tolerance: F) -> Self {
203        self.relative_tolerance = tolerance;
204        self
205    }
206
207    /// Set the absolute tolerance at convergence
208    pub fn with_absolute_tolerance(mut self, tolerance: F) -> Self {
209        self.absolute_tolerance = tolerance;
210        self
211    }
212
213    /// Set the number of consecutive good samples required for convergence
214    pub fn with_tolerance_window(mut self, window: usize) -> Self {
215        self.tolerance_window = window;
216        self
217    }
218
219    pub(crate) fn gk_config(&self) -> GaussKronrodConfig<F>
220    where
221        F: Copy,
222    {
223        GaussKronrodConfig::new(
224            self.integrator_order,
225            self.minimum_segment_width,
226            self.error_norm,
227            self.singularity_handling,
228        )
229    }
230}