SyncValsverifier → artifact → classifier → verdict
SyncVals · Trajectory

adaptive-quadrature

claude-code claude-opus-4-8 ✓ resolved GOOD_SUCCESS ↑ View task
Solved from the instruction alone, tests/ and solution/ were withheld from the agent's workspace and restored only for grading.
Reward = tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.
Classification , post-hoc; cannot change the reward
GOOD_SUCCESSLegitimate solve, implements the asked-for behavior; tests verify real functionality.
SubtypeLegitimate Solution
EvidenceAll 27 conformance tests pass: integrates_constant, integrates_linear, integrates_cubic_exactly, integrates_quartic, integrates_sine_over_half_period, integrates_exponential, integrates_reciprocal_to_ln, integrates_runge_function, integrates_gaussian_bell, integrates_oscillatory, reversed_bounds_negate_result, reversed_bounds_are_exact_negation, degenerate_interval_is_zero, negative_domain, very_small_interval, rejects_non_finite_lower_bound, rejects_non_finite_upper_bound, rejects_non_positive_tolerance, rejects_zero_max_depth, rejects_non_finite_integrand, reports_evaluations_and_depth, error_estimate_bounds_true_error, tighter_tolerance_costs_more_evaluations, depth_limit_flags_non_convergence, additive_over_adjacent_intervals, additive_with_many_splits, convenience_default_matches_explicit_config. Plus 2 smoke tests and 6 doctests. Verifier output: "all tests passed" with reward = 1.
Root causeThe agent correctly implemented the adaptive Simpson quadrature algorithm in both `integrate()` (driver) and `adaptive_simpson_recurse()` (recursive core). The implementation handles configuration validation, bound checking, sign conventions for reversed intervals, degenerate cases, exactly two evaluations per recursion level, Richardson error estimation, tolerance-based and depth-limit-based acceptance, and proper tracking of diagnostics.
RecommendationN/A - task is fine
Trajectory
Tool-by-tool agent trajectory
11 tool calls · 3 tool types · 13 steps
# Implement the adaptive Simpson recursion in `quadrature` ## Context The `quadrature` crate provides one-dimensional adaptive numerical integration for our scientific-computing stack. It lives at `/workspace/quadrature` in this environment. The public API, configuration, error types, result type, doctests, and the full integration-test suite are already in place. The crate compiles, but the **core recursive routine is unimplemented**, so the tests fail (the stub calls `todo!()`). Your job is to implement that one routine so the existing test suite passes. ## Where it lives Everything you need to change is in a single function: - File: `src/simpson.rs` - Function: `fn adaptive_simpson_recurse(...) -> Result<(f64, f64), QuadratureError>` Do **not** change the public API, the function signatures, the other modules (`config.rs`, `error.rs`, `outcome.rs`, `lib.rs`), or the tests. The supporting helpers `eval(...)` (evaluate the integrand, mapping non-finite results to a `QuadratureError::NonFiniteIntegrand`) and `simpson(...)` (a plain three-point Simpson estimate) are provided in the same module and should be reused. ## The algorithm Implement the classic globally-adaptive Simpson scheme: recursive bisection with Richardson-based local error control. The three-point Simpson rule `simpson(...)` and the checked `eval(...)` are provided and must be reused. For a panel `[a, b]` with midpoint `m`, you are given the endpoint/midpoint ordinates and the whole-panel Simpson estimate. One recursion step evaluates the two sub-midpoints, forms the two half-panel Simpson estimates, and uses the difference between the refined (two-panel) and the whole (one-panel) estimate as the local error proxy. From that proxy it either **accepts** the panel , applying the standard Richardson correction to the returned value and reporting the corresponding error estimate , or **bisects**, recursing on the two halves and combining their results. The depth limit forces acceptance; a panel accepted only because of the depth limit (not the tolerance) marks the result not-converged. The exact acceptance threshold, the Richardson correction factor, how the tolerance is apportioned to the sub-panels, and how `acc` (evaluation count, `max_depth_reached`, `converged`) is maintained are the standard ones for this scheme , match them; they are what the behavioural suite checks. Each recursion level performs exactly two new integrand evaluations (the sub-midpoints); the other ordinates are threaded through, not recomputed. ## Contract / correctness requirements The implementation must satisfy (all covered by `cargo test`): - **Accuracy.** Definite integrals of smooth functions match their analytic values to within the default tolerance (`1e-10`); the integration tests check against `1e-8`. Examples exercised: polynomials up to quartic, `sin`, `exp`, `1/x`, the Runge function `1/(1 + x^2)`, a Gaussian bell, and an oscillatory integrand. - **Orientation.** `∫_a^b = -∫_b^a` exactly (the public wrapper handles the sign; your recursion always sees `a < b`). A degenerate panel is `0`. - **Diagnostics.** `evaluations` and `max_depth_reached` reflect the actual work done; a genuinely adaptive run subdivides (more than the 3 initial evaluations, depth ≥ 1). A tighter tolerance must not require fewer evaluations than a looser one. - **Error estimate.** `error_estimate` is a finite, conservative bound on the true error for smooth integrands. - **Convergence flag.** With a demanding tolerance and a shallow `max_depth`, the routine still returns a finite estimate but reports `converged == false`. - **Additivity invariant.** `∫_a^c = ∫_a^b + ∫_b^c` to within `1e-7` for any split point(s) `b`. - **Robustness.** Non-finite bounds, non-positive tolerance, zero `max_depth`, and non-finite integrand values are reported as the corresponding `QuadratureError` (handled by the wrapper / `eval`; your recursion just needs to propagate `eval`'s errors with `?`). ## Build & test ``` cd /workspace/quadrature cargo test --release # unit + integration + doctests must all pass ``` The crate must build with no warnings under the existing lints (`#![forbid(unsafe_code)]`, `#![warn(missing_docs)]`).

/workspace/quadrature/src/simpson.rs

contents
1	//! Adaptive Simpson quadrature.
2	//!
3	//! This module implements globally-adaptive integration of a real-valued
4	//! function of one real variable over a finite interval `[a, b]` using
5	//! Simpson's rule with recursive interval bisection and local error control.
6	//!
7	//! # Method
8	//!
9	//! Simpson's rule approximates the integral over `[a, b]` using the endpoints
10	//! and the midpoint `m = (a + b) / 2`:
11	//!
12	//! ```text
13	//! S(a, b) = (b - a) / 6 * (f(a) + 4 f(m) + f(b))
14	//! ```
15	//!
16	//! The adaptive scheme compares the coarse estimate `S(a, b)` against the
17	//! refined estimate `S(a, m) + S(m, b)`. For a smooth integrand the two
18	//! agree to fourth order, and the difference yields a Richardson error
19	//! estimate. When
20	//!
21	//! ```text
22	//! |S(a, m) + S(m, b) - S(a, b)| <= 15 * tol_local
23	//! ```
24	//!
25	//! the refined estimate is accepted (with the standard `/15` Richardson
26	//! correction added back); otherwise the interval is bisected and each half
27	//! is integrated to half the local tolerance. This is the classic
28	//! Lyness adaptive Simpson algorithm.
29	//!
30	//! The public entry point is [`integrate`]. The recursive workhorse,
31	//! [`adaptive_simpson_recurse`], is invoked once per top-level call and
32	//! carries the running diagnostics.
33	
34	use crate::config::Config;
35	use crate::error::QuadratureError;
36	use crate::outcome::IntegrationOutcome;
37	
38	/// Evaluate the integrand at `x`, converting a non-finite result into a hard
39	/// error. Centralising this keeps the recursive code free of repeated checks.
40	#[inline]
41	fn eval<F>(f: &F, x: f64) -> Result<f64, QuadratureError>
42	where
43	    F: Fn(f64) -> f64,
44	{
45	    let y = f(x);
46	    if y.is_finite() {
47	        Ok(y)
48	    } else {
49	        Err(QuadratureError::NonFiniteIntegrand { x, value: y })
50	    }
51	}
52	
53	/// Plain (non-adaptive) Simpson estimate over `[a, b]` given precomputed
54	/// endpoint and midpoint ordinates.
55	///
56	/// `fa = f(a)`, `fm = f((a + b) / 2)`, `fb = f(b)`.
57	#[inline]
58	fn simpson(a: f64, b: f64, fa: f64, fm: f64, fb: f64) -> f64 {
59	    (b - a) / 6.0 * (fa + 4.0 * fm + fb)
60	}
61	
62	/// Mutable state threaded through the recursion to gather diagnostics without
63	/// returning a tuple from every call.
64	struct Accumulator {
65	    evaluations: u64,
66	    max_depth_reached: u32,
67	    converged: bool,
68	}
69	
70	/// Integrate `f` over `[a, b]` to the accuracy requested by `config`.
71	///
72	/// On success returns an [`IntegrationOutcome`] carrying the estimate and
73	/// diagnostics. Returns a [`QuadratureError`] if the bounds are non-finite,
74	/// the configuration is invalid, or the integrand returns a non-finite value.
75	///
76	/// Reversed bounds (`a > b`) are handled by the usual sign convention:
77	/// `∫_a^b = -∫_b^a`. Degenerate bounds (`a == b`) integrate to exactly `0`.
78	///
79	/// # Examples
80	///
81	/// ```
82	/// use quadrature::{integrate, Config};
83	/// // ∫_0^1 x^2 dx = 1/3
84	/// let out = integrate(|x| x * x, 0.0, 1.0, &Config::new()).unwrap();
85	/// assert!((out.value - 1.0 / 3.0).abs() < 1e-9);
86	/// ```
87	pub fn integrate<F>(
88	    f: F,
89	    a: f64,
90	    b: f64,
91	    config: &Config,
92	) -> Result<IntegrationOutcome, QuadratureError>
93	where
94	    F: Fn(f64) -> f64,
95	{
96	    // TODO(sci-1421): implement the integration driver. Validate the bounds and
97	    // config, handle the degenerate (a == b) and reversed-bound (a > b) cases,
98	    // seed the initial panel evaluations, drive `adaptive_simpson_recurse`, and
99	    // assemble the `IntegrationOutcome`. See the doc comment and the suite.
100	    let _ = (&f, a, b, config);
101	    todo!("implement the integrate driver (sci-1421)")
102	}
103	
104	/// Recursive core of the adaptive Simpson scheme.
105	///
106	/// Integrates `f` over `[a, b]`, where `fa`, `fm`, `fb` are the already-known
107	/// ordinates at `a`, the midpoint `m = (a + b) / 2`, and `b`, and `whole` is
108	/// the Simpson estimate `simpson(a, b, fa, fm, fb)` for the current interval
109	/// (passed in so it is not recomputed).
110	///
111	/// Evaluate the two sub-midpoints (via [`eval`], so non-finite values surface as
112	/// errors, incrementing `acc.evaluations`), form the two half-panel Simpson
113	/// estimates, and use the difference between their sum and `whole` as the local
114	/// error proxy. Either accept the panel , applying the standard Richardson
115	/// correction to the returned value and reporting the matching error estimate , 
116	/// or bisect, recursing on `[a, m]` and `[m, b]` with the tolerance apportioned
117	/// in the usual way and combining the children. The depth limit forces
118	/// acceptance; a panel accepted only on the depth limit sets `acc.converged =
119	/// false`, and `acc.max_depth_reached` tracks the depth. Use the standard
120	/// adaptive-Simpson constants for the acceptance threshold, the correction
121	/// factor, and the tolerance apportionment; the behavioural suite pins them.
122	///
123	/// The two halves reuse `fa, flm, fm` and `fm, frm, fb` respectively, so each
124	/// recursion level performs exactly two new integrand evaluations.
125	///
126	/// Returns `(value, error_estimate)` for the subinterval `[a, b]`.
127	#[allow(clippy::too_many_arguments)]
128	fn adaptive_simpson_recurse<F>(
129	    f: &F,
130	    a: f64,
131	    b: f64,
132	    fa: f64,
133	    fm: f64,
134	    fb: f64,
135	    whole: f64,
136	    tol: f64,
137	    max_depth: u32,
138	    depth: u32,
139	    acc: &mut Accumulator,
140	) -> Result<(f64, f64), QuadratureError>
141	where
142	    F: Fn(f64) -> f64,
143	{
144	    // TODO(sci-1421): implement the recursive adaptive Simpson step (see doc).
145	    let _ = (f, a, b, fa, fm, fb, whole, tol, max_depth, depth, acc);
146	    todo!("implement adaptive_simpson_recurse (sci-1421)")
147	}
148

/workspace/quadrature/src/config.rs

contents
1	//! Configuration for an adaptive integration run.
2	
3	use crate::error::QuadratureError;
4	
5	/// Default absolute error tolerance used by [`Config::new`].
6	pub const DEFAULT_TOLERANCE: f64 = 1e-10;
7	
8	/// Default maximum recursion depth used by [`Config::new`].
9	pub const DEFAULT_MAX_DEPTH: u32 = 50;
10	
11	/// Tuning parameters for the adaptive Simpson integrator.
12	///
13	/// A `Config` bundles the convergence criteria for a single integration.
14	/// Construct one with [`Config::new`] (sensible defaults) and refine it with
15	/// the chained setters, e.g.
16	///
17	/// ```
18	/// use quadrature::Config;
19	/// let cfg = Config::new()
20	///     .with_tolerance(1e-8)
21	///     .with_max_depth(30);
22	/// assert_eq!(cfg.tolerance(), 1e-8);
23	/// ```
24	#[derive(Debug, Clone, Copy, PartialEq)]
25	pub struct Config {
26	    tolerance: f64,
27	    max_depth: u32,
28	}
29	
30	impl Config {
31	    /// Create a configuration with the crate defaults
32	    /// ([`DEFAULT_TOLERANCE`], [`DEFAULT_MAX_DEPTH`]).
33	    pub fn new() -> Self {
34	        Self {
35	            tolerance: DEFAULT_TOLERANCE,
36	            max_depth: DEFAULT_MAX_DEPTH,
37	        }
38	    }
39	
40	    /// Set the absolute error tolerance for the whole integral.
41	    ///
42	    /// Smaller values demand a more accurate result at the cost of more
43	    /// integrand evaluations.
44	    #[must_use]
45	    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
46	        self.tolerance = tolerance;
47	        self
48	    }
49	
50	    /// Set the maximum recursion depth. Each level can at most double the
51	    /// number of subintervals, so this bounds the work performed.
52	    #[must_use]
53	    pub fn with_max_depth(mut self, max_depth: u32) -> Self {
54	        self.max_depth = max_depth;
55	        self
56	    }
57	
58	    /// The configured absolute error tolerance.
59	    pub fn tolerance(&self) -> f64 {
60	        self.tolerance
61	    }
62	
63	    /// The configured maximum recursion depth.
64	    pub fn max_depth(&self) -> u32 {
65	        self.max_depth
66	    }
67	
68	    /// Validate the configuration, returning an error if any field is out of
69	    /// range. Called internally before an integration begins.
70	    pub(crate) fn validate(&self) -> Result<(), QuadratureError> {
71	        if !self.tolerance.is_finite() || self.tolerance <= 0.0 {
72	            return Err(QuadratureError::InvalidTolerance(self.tolerance));
73	        }
74	        if self.max_depth == 0 {
75	            return Err(QuadratureError::InvalidMaxDepth);
76	        }
77	        Ok(())
78	    }
79	}
80	
81	impl Default for Config {
82	    fn default() -> Self {
83	        Self::new()
84	    }
85	}
86

1	//! Error types for the quadrature crate.
2	
3	use thiserror::Error;
4	
5	/// Errors that can arise while configuring or running an integration.
6	///
7	/// These cover the cases where the requested integration cannot be carried
8	/// out reliably. They are intentionally distinct from a *successful but
9	/// imprecise* result, which is reported through
10	/// [`IntegrationOutcome`](crate::IntegrationOutcome) instead.
11	#[derive(Debug, Error, Clone, PartialEq)]
12	#[non_exhaustive]
13	pub enum QuadratureError {
14	    /// One of the integration bounds was not a finite number (it was `NaN`
15	    /// or an infinity). Infinite-domain integration is not supported.
16	    #[error("integration bound `{name}` must be finite, got {value}")]
17	    NonFiniteBound {
18	        /// Which bound was offending (`"a"` or `"b"`).
19	        name: &'static str,
20	        /// The offending value.
21	        value: f64,
22	    },
23	
24	    /// The requested absolute tolerance was not a strictly positive,
25	    /// finite number.
26	    #[error("tolerance must be finite and strictly positive, got {0}")]
27	    InvalidTolerance(f64),
28	
29	    /// The requested recursion depth limit was zero. At least one level of
30	    /// subdivision must be permitted.
31	    #[error("max_depth must be at least 1, got 0")]
32	    InvalidMaxDepth,
33	
34	    /// The integrand returned a non-finite value (`NaN` or infinity) at the
35	    /// given abscissa, so the quadrature cannot proceed.
36	    #[error("integrand returned a non-finite value {value} at x = {x}")]
37	    NonFiniteIntegrand {
38	        /// The point at which the integrand misbehaved.
39	        x: f64,
40	        /// The non-finite value returned.
41	        value: f64,
42	    },
43	}
44

1	//! The result type returned by a successful integration.
2	
3	/// The outcome of a converged (or depth-limited) integration.
4	///
5	/// The crate distinguishes hard failures (reported as
6	/// [`QuadratureError`](crate::QuadratureError)) from a numerically completed
7	/// run that may or may not have met its tolerance. This struct carries the
8	/// estimate together with diagnostics describing how it was obtained.
9	#[derive(Debug, Clone, Copy, PartialEq)]
10	#[non_exhaustive]
11	pub struct IntegrationOutcome {
12	    /// The estimated value of the definite integral.
13	    pub value: f64,
14	
15	    /// A conservative estimate of the absolute error in [`value`](Self::value),
16	    /// derived from the Richardson-style comparison of the coarse and refined
17	    /// Simpson estimates.
18	    pub error_estimate: f64,
19	
20	    /// Total number of integrand evaluations performed.
21	    pub evaluations: u64,
22	
23	    /// The deepest recursion level reached during subdivision.
24	    pub max_depth_reached: u32,
25	
26	    /// Whether every subinterval met the local tolerance before the depth
27	    /// limit was hit. When `false`, the result is the best available estimate
28	    /// but the global tolerance may not be satisfied.
29	    pub converged: bool,
30	}
31	
32	impl IntegrationOutcome {
33	    /// Convenience accessor returning just the integral estimate.
34	    ///
35	    /// Useful when the caller does not care about diagnostics:
36	    ///
37	    /// ```
38	    /// use quadrature::{integrate, Config};
39	    /// let v = integrate(|x| x, 0.0, 1.0, &Config::new()).unwrap().estimate();
40	    /// assert!((v - 0.5).abs() < 1e-9);
41	    /// ```
42	    pub fn estimate(&self) -> f64 {
43	        self.value
44	    }
45	}
46

1	//! # quadrature
2	//!
3	//! Adaptive numerical integration of one-dimensional definite integrals.
4	//!
5	//! The crate provides a single high-level entry point, [`integrate`], which
6	//! evaluates `∫_a^b f(x) dx` for a finite interval using a globally-adaptive
7	//! Simpson scheme with recursive bisection and local error control. The
8	//! returned [`IntegrationOutcome`] carries both the estimate and diagnostics
9	//! (error estimate, evaluation count, recursion depth, convergence flag).
10	//!
11	//! ```
12	//! use quadrature::{integrate, Config};
13	//! use std::f64::consts::PI;
14	//!
15	//! // ∫_0^pi sin(x) dx = 2
16	//! let out = integrate(f64::sin, 0.0, PI, &Config::new()).unwrap();
17	//! assert!((out.value - 2.0).abs() < 1e-9);
18	//! assert!(out.converged);
19	//! ```
20	//!
21	//! Convergence behaviour is tuned through [`Config`]:
22	//!
23	//! ```
24	//! use quadrature::{integrate, Config};
25	//! let cfg = Config::new().with_tolerance(1e-6).with_max_depth(20);
26	//! let out = integrate(|x| (-x * x).exp(), -3.0, 3.0, &cfg).unwrap();
27	//! assert!(out.value > 1.7 && out.value < 1.8);
28	//! ```
29	//!
30	//! See the [`simpson`] module for a description of the algorithm.
31	
32	#![forbid(unsafe_code)]
33	#![warn(missing_docs)]
34	
35	mod config;
36	mod error;
37	mod outcome;
38	mod simpson;
39	
40	pub use config::{Config, DEFAULT_MAX_DEPTH, DEFAULT_TOLERANCE};
41	pub use error::QuadratureError;
42	pub use outcome::IntegrationOutcome;
43	pub use simpson::integrate;
44	
45	/// Crate-level convenience: integrate with the default [`Config`].
46	///
47	/// Equivalent to `integrate(f, a, b, &Config::new())`.
48	///
49	/// ```
50	/// use quadrature::integrate_default;
51	/// let out = integrate_default(|x| x * x * x, 0.0, 2.0).unwrap();
52	/// // ∫_0^2 x^3 dx = 4
53	/// assert!((out.value - 4.0).abs() < 1e-9);
54	/// ```
55	pub fn integrate_default<F>(f: F, a: f64, b: f64) -> Result<IntegrationOutcome, QuadratureError>
56	where
57	    F: Fn(f64) -> f64,
58	{
59	    integrate(f, a, b, &Config::new())
60	}
61

/workspace/quadrature/src/error.rs

contents
1	//! Configuration for an adaptive integration run.
2	
3	use crate::error::QuadratureError;
4	
5	/// Default absolute error tolerance used by [`Config::new`].
6	pub const DEFAULT_TOLERANCE: f64 = 1e-10;
7	
8	/// Default maximum recursion depth used by [`Config::new`].
9	pub const DEFAULT_MAX_DEPTH: u32 = 50;
10	
11	/// Tuning parameters for the adaptive Simpson integrator.
12	///
13	/// A `Config` bundles the convergence criteria for a single integration.
14	/// Construct one with [`Config::new`] (sensible defaults) and refine it with
15	/// the chained setters, e.g.
16	///
17	/// ```
18	/// use quadrature::Config;
19	/// let cfg = Config::new()
20	///     .with_tolerance(1e-8)
21	///     .with_max_depth(30);
22	/// assert_eq!(cfg.tolerance(), 1e-8);
23	/// ```
24	#[derive(Debug, Clone, Copy, PartialEq)]
25	pub struct Config {
26	    tolerance: f64,
27	    max_depth: u32,
28	}
29	
30	impl Config {
31	    /// Create a configuration with the crate defaults
32	    /// ([`DEFAULT_TOLERANCE`], [`DEFAULT_MAX_DEPTH`]).
33	    pub fn new() -> Self {
34	        Self {
35	            tolerance: DEFAULT_TOLERANCE,
36	            max_depth: DEFAULT_MAX_DEPTH,
37	        }
38	    }
39	
40	    /// Set the absolute error tolerance for the whole integral.
41	    ///
42	    /// Smaller values demand a more accurate result at the cost of more
43	    /// integrand evaluations.
44	    #[must_use]
45	    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
46	        self.tolerance = tolerance;
47	        self
48	    }
49	
50	    /// Set the maximum recursion depth. Each level can at most double the
51	    /// number of subintervals, so this bounds the work performed.
52	    #[must_use]
53	    pub fn with_max_depth(mut self, max_depth: u32) -> Self {
54	        self.max_depth = max_depth;
55	        self
56	    }
57	
58	    /// The configured absolute error tolerance.
59	    pub fn tolerance(&self) -> f64 {
60	        self.tolerance
61	    }
62	
63	    /// The configured maximum recursion depth.
64	    pub fn max_depth(&self) -> u32 {
65	        self.max_depth
66	    }
67	
68	    /// Validate the configuration, returning an error if any field is out of
69	    /// range. Called internally before an integration begins.
70	    pub(crate) fn validate(&self) -> Result<(), QuadratureError> {
71	        if !self.tolerance.is_finite() || self.tolerance <= 0.0 {
72	            return Err(QuadratureError::InvalidTolerance(self.tolerance));
73	        }
74	        if self.max_depth == 0 {
75	            return Err(QuadratureError::InvalidMaxDepth);
76	        }
77	        Ok(())
78	    }
79	}
80	
81	impl Default for Config {
82	    fn default() -> Self {
83	        Self::new()
84	    }
85	}
86

1	//! Error types for the quadrature crate.
2	
3	use thiserror::Error;
4	
5	/// Errors that can arise while configuring or running an integration.
6	///
7	/// These cover the cases where the requested integration cannot be carried
8	/// out reliably. They are intentionally distinct from a *successful but
9	/// imprecise* result, which is reported through
10	/// [`IntegrationOutcome`](crate::IntegrationOutcome) instead.
11	#[derive(Debug, Error, Clone, PartialEq)]
12	#[non_exhaustive]
13	pub enum QuadratureError {
14	    /// One of the integration bounds was not a finite number (it was `NaN`
15	    /// or an infinity). Infinite-domain integration is not supported.
16	    #[error("integration bound `{name}` must be finite, got {value}")]
17	    NonFiniteBound {
18	        /// Which bound was offending (`"a"` or `"b"`).
19	        name: &'static str,
20	        /// The offending value.
21	        value: f64,
22	    },
23	
24	    /// The requested absolute tolerance was not a strictly positive,
25	    /// finite number.
26	    #[error("tolerance must be finite and strictly positive, got {0}")]
27	    InvalidTolerance(f64),
28	
29	    /// The requested recursion depth limit was zero. At least one level of
30	    /// subdivision must be permitted.
31	    #[error("max_depth must be at least 1, got 0")]
32	    InvalidMaxDepth,
33	
34	    /// The integrand returned a non-finite value (`NaN` or infinity) at the
35	    /// given abscissa, so the quadrature cannot proceed.
36	    #[error("integrand returned a non-finite value {value} at x = {x}")]
37	    NonFiniteIntegrand {
38	        /// The point at which the integrand misbehaved.
39	        x: f64,
40	        /// The non-finite value returned.
41	        value: f64,
42	    },
43	}
44

1	//! The result type returned by a successful integration.
2	
3	/// The outcome of a converged (or depth-limited) integration.
4	///
5	/// The crate distinguishes hard failures (reported as
6	/// [`QuadratureError`](crate::QuadratureError)) from a numerically completed
7	/// run that may or may not have met its tolerance. This struct carries the
8	/// estimate together with diagnostics describing how it was obtained.
9	#[derive(Debug, Clone, Copy, PartialEq)]
10	#[non_exhaustive]
11	pub struct IntegrationOutcome {
12	    /// The estimated value of the definite integral.
13	    pub value: f64,
14	
15	    /// A conservative estimate of the absolute error in [`value`](Self::value),
16	    /// derived from the Richardson-style comparison of the coarse and refined
17	    /// Simpson estimates.
18	    pub error_estimate: f64,
19	
20	    /// Total number of integrand evaluations performed.
21	    pub evaluations: u64,
22	
23	    /// The deepest recursion level reached during subdivision.
24	    pub max_depth_reached: u32,
25	
26	    /// Whether every subinterval met the local tolerance before the depth
27	    /// limit was hit. When `false`, the result is the best available estimate
28	    /// but the global tolerance may not be satisfied.
29	    pub converged: bool,
30	}
31	
32	impl IntegrationOutcome {
33	    /// Convenience accessor returning just the integral estimate.
34	    ///
35	    /// Useful when the caller does not care about diagnostics:
36	    ///
37	    /// ```
38	    /// use quadrature::{integrate, Config};
39	    /// let v = integrate(|x| x, 0.0, 1.0, &Config::new()).unwrap().estimate();
40	    /// assert!((v - 0.5).abs() < 1e-9);
41	    /// ```
42	    pub fn estimate(&self) -> f64 {
43	        self.value
44	    }
45	}
46

1	//! # quadrature
2	//!
3	//! Adaptive numerical integration of one-dimensional definite integrals.
4	//!
5	//! The crate provides a single high-level entry point, [`integrate`], which
6	//! evaluates `∫_a^b f(x) dx` for a finite interval using a globally-adaptive
7	//! Simpson scheme with recursive bisection and local error control. The
8	//! returned [`IntegrationOutcome`] carries both the estimate and diagnostics
9	//! (error estimate, evaluation count, recursion depth, convergence flag).
10	//!
11	//! ```
12	//! use quadrature::{integrate, Config};
13	//! use std::f64::consts::PI;
14	//!
15	//! // ∫_0^pi sin(x) dx = 2
16	//! let out = integrate(f64::sin, 0.0, PI, &Config::new()).unwrap();
17	//! assert!((out.value - 2.0).abs() < 1e-9);
18	//! assert!(out.converged);
19	//! ```
20	//!
21	//! Convergence behaviour is tuned through [`Config`]:
22	//!
23	//! ```
24	//! use quadrature::{integrate, Config};
25	//! let cfg = Config::new().with_tolerance(1e-6).with_max_depth(20);
26	//! let out = integrate(|x| (-x * x).exp(), -3.0, 3.0, &cfg).unwrap();
27	//! assert!(out.value > 1.7 && out.value < 1.8);
28	//! ```
29	//!
30	//! See the [`simpson`] module for a description of the algorithm.
31	
32	#![forbid(unsafe_code)]
33	#![warn(missing_docs)]
34	
35	mod config;
36	mod error;
37	mod outcome;
38	mod simpson;
39	
40	pub use config::{Config, DEFAULT_MAX_DEPTH, DEFAULT_TOLERANCE};
41	pub use error::QuadratureError;
42	pub use outcome::IntegrationOutcome;
43	pub use simpson::integrate;
44	
45	/// Crate-level convenience: integrate with the default [`Config`].
46	///
47	/// Equivalent to `integrate(f, a, b, &Config::new())`.
48	///
49	/// ```
50	/// use quadrature::integrate_default;
51	/// let out = integrate_default(|x| x * x * x, 0.0, 2.0).unwrap();
52	/// // ∫_0^2 x^3 dx = 4
53	/// assert!((out.value - 4.0).abs() < 1e-9);
54	/// ```
55	pub fn integrate_default<F>(f: F, a: f64, b: f64) -> Result<IntegrationOutcome, QuadratureError>
56	where
57	    F: Fn(f64) -> f64,
58	{
59	    integrate(f, a, b, &Config::new())
60	}
61

/workspace/quadrature/src/outcome.rs

contents
1	//! Configuration for an adaptive integration run.
2	
3	use crate::error::QuadratureError;
4	
5	/// Default absolute error tolerance used by [`Config::new`].
6	pub const DEFAULT_TOLERANCE: f64 = 1e-10;
7	
8	/// Default maximum recursion depth used by [`Config::new`].
9	pub const DEFAULT_MAX_DEPTH: u32 = 50;
10	
11	/// Tuning parameters for the adaptive Simpson integrator.
12	///
13	/// A `Config` bundles the convergence criteria for a single integration.
14	/// Construct one with [`Config::new`] (sensible defaults) and refine it with
15	/// the chained setters, e.g.
16	///
17	/// ```
18	/// use quadrature::Config;
19	/// let cfg = Config::new()
20	///     .with_tolerance(1e-8)
21	///     .with_max_depth(30);
22	/// assert_eq!(cfg.tolerance(), 1e-8);
23	/// ```
24	#[derive(Debug, Clone, Copy, PartialEq)]
25	pub struct Config {
26	    tolerance: f64,
27	    max_depth: u32,
28	}
29	
30	impl Config {
31	    /// Create a configuration with the crate defaults
32	    /// ([`DEFAULT_TOLERANCE`], [`DEFAULT_MAX_DEPTH`]).
33	    pub fn new() -> Self {
34	        Self {
35	            tolerance: DEFAULT_TOLERANCE,
36	            max_depth: DEFAULT_MAX_DEPTH,
37	        }
38	    }
39	
40	    /// Set the absolute error tolerance for the whole integral.
41	    ///
42	    /// Smaller values demand a more accurate result at the cost of more
43	    /// integrand evaluations.
44	    #[must_use]
45	    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
46	        self.tolerance = tolerance;
47	        self
48	    }
49	
50	    /// Set the maximum recursion depth. Each level can at most double the
51	    /// number of subintervals, so this bounds the work performed.
52	    #[must_use]
53	    pub fn with_max_depth(mut self, max_depth: u32) -> Self {
54	        self.max_depth = max_depth;
55	        self
56	    }
57	
58	    /// The configured absolute error tolerance.
59	    pub fn tolerance(&self) -> f64 {
60	        self.tolerance
61	    }
62	
63	    /// The configured maximum recursion depth.
64	    pub fn max_depth(&self) -> u32 {
65	        self.max_depth
66	    }
67	
68	    /// Validate the configuration, returning an error if any field is out of
69	    /// range. Called internally before an integration begins.
70	    pub(crate) fn validate(&self) -> Result<(), QuadratureError> {
71	        if !self.tolerance.is_finite() || self.tolerance <= 0.0 {
72	            return Err(QuadratureError::InvalidTolerance(self.tolerance));
73	        }
74	        if self.max_depth == 0 {
75	            return Err(QuadratureError::InvalidMaxDepth);
76	        }
77	        Ok(())
78	    }
79	}
80	
81	impl Default for Config {
82	    fn default() -> Self {
83	        Self::new()
84	    }
85	}
86

1	//! Error types for the quadrature crate.
2	
3	use thiserror::Error;
4	
5	/// Errors that can arise while configuring or running an integration.
6	///
7	/// These cover the cases where the requested integration cannot be carried
8	/// out reliably. They are intentionally distinct from a *successful but
9	/// imprecise* result, which is reported through
10	/// [`IntegrationOutcome`](crate::IntegrationOutcome) instead.
11	#[derive(Debug, Error, Clone, PartialEq)]
12	#[non_exhaustive]
13	pub enum QuadratureError {
14	    /// One of the integration bounds was not a finite number (it was `NaN`
15	    /// or an infinity). Infinite-domain integration is not supported.
16	    #[error("integration bound `{name}` must be finite, got {value}")]
17	    NonFiniteBound {
18	        /// Which bound was offending (`"a"` or `"b"`).
19	        name: &'static str,
20	        /// The offending value.
21	        value: f64,
22	    },
23	
24	    /// The requested absolute tolerance was not a strictly positive,
25	    /// finite number.
26	    #[error("tolerance must be finite and strictly positive, got {0}")]
27	    InvalidTolerance(f64),
28	
29	    /// The requested recursion depth limit was zero. At least one level of
30	    /// subdivision must be permitted.
31	    #[error("max_depth must be at least 1, got 0")]
32	    InvalidMaxDepth,
33	
34	    /// The integrand returned a non-finite value (`NaN` or infinity) at the
35	    /// given abscissa, so the quadrature cannot proceed.
36	    #[error("integrand returned a non-finite value {value} at x = {x}")]
37	    NonFiniteIntegrand {
38	        /// The point at which the integrand misbehaved.
39	        x: f64,
40	        /// The non-finite value returned.
41	        value: f64,
42	    },
43	}
44

1	//! The result type returned by a successful integration.
2	
3	/// The outcome of a converged (or depth-limited) integration.
4	///
5	/// The crate distinguishes hard failures (reported as
6	/// [`QuadratureError`](crate::QuadratureError)) from a numerically completed
7	/// run that may or may not have met its tolerance. This struct carries the
8	/// estimate together with diagnostics describing how it was obtained.
9	#[derive(Debug, Clone, Copy, PartialEq)]
10	#[non_exhaustive]
11	pub struct IntegrationOutcome {
12	    /// The estimated value of the definite integral.
13	    pub value: f64,
14	
15	    /// A conservative estimate of the absolute error in [`value`](Self::value),
16	    /// derived from the Richardson-style comparison of the coarse and refined
17	    /// Simpson estimates.
18	    pub error_estimate: f64,
19	
20	    /// Total number of integrand evaluations performed.
21	    pub evaluations: u64,
22	
23	    /// The deepest recursion level reached during subdivision.
24	    pub max_depth_reached: u32,
25	
26	    /// Whether every subinterval met the local tolerance before the depth
27	    /// limit was hit. When `false`, the result is the best available estimate
28	    /// but the global tolerance may not be satisfied.
29	    pub converged: bool,
30	}
31	
32	impl IntegrationOutcome {
33	    /// Convenience accessor returning just the integral estimate.
34	    ///
35	    /// Useful when the caller does not care about diagnostics:
36	    ///
37	    /// ```
38	    /// use quadrature::{integrate, Config};
39	    /// let v = integrate(|x| x, 0.0, 1.0, &Config::new()).unwrap().estimate();
40	    /// assert!((v - 0.5).abs() < 1e-9);
41	    /// ```
42	    pub fn estimate(&self) -> f64 {
43	        self.value
44	    }
45	}
46

1	//! # quadrature
2	//!
3	//! Adaptive numerical integration of one-dimensional definite integrals.
4	//!
5	//! The crate provides a single high-level entry point, [`integrate`], which
6	//! evaluates `∫_a^b f(x) dx` for a finite interval using a globally-adaptive
7	//! Simpson scheme with recursive bisection and local error control. The
8	//! returned [`IntegrationOutcome`] carries both the estimate and diagnostics
9	//! (error estimate, evaluation count, recursion depth, convergence flag).
10	//!
11	//! ```
12	//! use quadrature::{integrate, Config};
13	//! use std::f64::consts::PI;
14	//!
15	//! // ∫_0^pi sin(x) dx = 2
16	//! let out = integrate(f64::sin, 0.0, PI, &Config::new()).unwrap();
17	//! assert!((out.value - 2.0).abs() < 1e-9);
18	//! assert!(out.converged);
19	//! ```
20	//!
21	//! Convergence behaviour is tuned through [`Config`]:
22	//!
23	//! ```
24	//! use quadrature::{integrate, Config};
25	//! let cfg = Config::new().with_tolerance(1e-6).with_max_depth(20);
26	//! let out = integrate(|x| (-x * x).exp(), -3.0, 3.0, &cfg).unwrap();
27	//! assert!(out.value > 1.7 && out.value < 1.8);
28	//! ```
29	//!
30	//! See the [`simpson`] module for a description of the algorithm.
31	
32	#![forbid(unsafe_code)]
33	#![warn(missing_docs)]
34	
35	mod config;
36	mod error;
37	mod outcome;
38	mod simpson;
39	
40	pub use config::{Config, DEFAULT_MAX_DEPTH, DEFAULT_TOLERANCE};
41	pub use error::QuadratureError;
42	pub use outcome::IntegrationOutcome;
43	pub use simpson::integrate;
44	
45	/// Crate-level convenience: integrate with the default [`Config`].
46	///
47	/// Equivalent to `integrate(f, a, b, &Config::new())`.
48	///
49	/// ```
50	/// use quadrature::integrate_default;
51	/// let out = integrate_default(|x| x * x * x, 0.0, 2.0).unwrap();
52	/// // ∫_0^2 x^3 dx = 4
53	/// assert!((out.value - 4.0).abs() < 1e-9);
54	/// ```
55	pub fn integrate_default<F>(f: F, a: f64, b: f64) -> Result<IntegrationOutcome, QuadratureError>
56	where
57	    F: Fn(f64) -> f64,
58	{
59	    integrate(f, a, b, &Config::new())
60	}
61

/workspace/quadrature/src/lib.rs

contents
1	//! Configuration for an adaptive integration run.
2	
3	use crate::error::QuadratureError;
4	
5	/// Default absolute error tolerance used by [`Config::new`].
6	pub const DEFAULT_TOLERANCE: f64 = 1e-10;
7	
8	/// Default maximum recursion depth used by [`Config::new`].
9	pub const DEFAULT_MAX_DEPTH: u32 = 50;
10	
11	/// Tuning parameters for the adaptive Simpson integrator.
12	///
13	/// A `Config` bundles the convergence criteria for a single integration.
14	/// Construct one with [`Config::new`] (sensible defaults) and refine it with
15	/// the chained setters, e.g.
16	///
17	/// ```
18	/// use quadrature::Config;
19	/// let cfg = Config::new()
20	///     .with_tolerance(1e-8)
21	///     .with_max_depth(30);
22	/// assert_eq!(cfg.tolerance(), 1e-8);
23	/// ```
24	#[derive(Debug, Clone, Copy, PartialEq)]
25	pub struct Config {
26	    tolerance: f64,
27	    max_depth: u32,
28	}
29	
30	impl Config {
31	    /// Create a configuration with the crate defaults
32	    /// ([`DEFAULT_TOLERANCE`], [`DEFAULT_MAX_DEPTH`]).
33	    pub fn new() -> Self {
34	        Self {
35	            tolerance: DEFAULT_TOLERANCE,
36	            max_depth: DEFAULT_MAX_DEPTH,
37	        }
38	    }
39	
40	    /// Set the absolute error tolerance for the whole integral.
41	    ///
42	    /// Smaller values demand a more accurate result at the cost of more
43	    /// integrand evaluations.
44	    #[must_use]
45	    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
46	        self.tolerance = tolerance;
47	        self
48	    }
49	
50	    /// Set the maximum recursion depth. Each level can at most double the
51	    /// number of subintervals, so this bounds the work performed.
52	    #[must_use]
53	    pub fn with_max_depth(mut self, max_depth: u32) -> Self {
54	        self.max_depth = max_depth;
55	        self
56	    }
57	
58	    /// The configured absolute error tolerance.
59	    pub fn tolerance(&self) -> f64 {
60	        self.tolerance
61	    }
62	
63	    /// The configured maximum recursion depth.
64	    pub fn max_depth(&self) -> u32 {
65	        self.max_depth
66	    }
67	
68	    /// Validate the configuration, returning an error if any field is out of
69	    /// range. Called internally before an integration begins.
70	    pub(crate) fn validate(&self) -> Result<(), QuadratureError> {
71	        if !self.tolerance.is_finite() || self.tolerance <= 0.0 {
72	            return Err(QuadratureError::InvalidTolerance(self.tolerance));
73	        }
74	        if self.max_depth == 0 {
75	            return Err(QuadratureError::InvalidMaxDepth);
76	        }
77	        Ok(())
78	    }
79	}
80	
81	impl Default for Config {
82	    fn default() -> Self {
83	        Self::new()
84	    }
85	}
86

1	//! Error types for the quadrature crate.
2	
3	use thiserror::Error;
4	
5	/// Errors that can arise while configuring or running an integration.
6	///
7	/// These cover the cases where the requested integration cannot be carried
8	/// out reliably. They are intentionally distinct from a *successful but
9	/// imprecise* result, which is reported through
10	/// [`IntegrationOutcome`](crate::IntegrationOutcome) instead.
11	#[derive(Debug, Error, Clone, PartialEq)]
12	#[non_exhaustive]
13	pub enum QuadratureError {
14	    /// One of the integration bounds was not a finite number (it was `NaN`
15	    /// or an infinity). Infinite-domain integration is not supported.
16	    #[error("integration bound `{name}` must be finite, got {value}")]
17	    NonFiniteBound {
18	        /// Which bound was offending (`"a"` or `"b"`).
19	        name: &'static str,
20	        /// The offending value.
21	        value: f64,
22	    },
23	
24	    /// The requested absolute tolerance was not a strictly positive,
25	    /// finite number.
26	    #[error("tolerance must be finite and strictly positive, got {0}")]
27	    InvalidTolerance(f64),
28	
29	    /// The requested recursion depth limit was zero. At least one level of
30	    /// subdivision must be permitted.
31	    #[error("max_depth must be at least 1, got 0")]
32	    InvalidMaxDepth,
33	
34	    /// The integrand returned a non-finite value (`NaN` or infinity) at the
35	    /// given abscissa, so the quadrature cannot proceed.
36	    #[error("integrand returned a non-finite value {value} at x = {x}")]
37	    NonFiniteIntegrand {
38	        /// The point at which the integrand misbehaved.
39	        x: f64,
40	        /// The non-finite value returned.
41	        value: f64,
42	    },
43	}
44

1	//! The result type returned by a successful integration.
2	
3	/// The outcome of a converged (or depth-limited) integration.
4	///
5	/// The crate distinguishes hard failures (reported as
6	/// [`QuadratureError`](crate::QuadratureError)) from a numerically completed
7	/// run that may or may not have met its tolerance. This struct carries the
8	/// estimate together with diagnostics describing how it was obtained.
9	#[derive(Debug, Clone, Copy, PartialEq)]
10	#[non_exhaustive]
11	pub struct IntegrationOutcome {
12	    /// The estimated value of the definite integral.
13	    pub value: f64,
14	
15	    /// A conservative estimate of the absolute error in [`value`](Self::value),
16	    /// derived from the Richardson-style comparison of the coarse and refined
17	    /// Simpson estimates.
18	    pub error_estimate: f64,
19	
20	    /// Total number of integrand evaluations performed.
21	    pub evaluations: u64,
22	
23	    /// The deepest recursion level reached during subdivision.
24	    pub max_depth_reached: u32,
25	
26	    /// Whether every subinterval met the local tolerance before the depth
27	    /// limit was hit. When `false`, the result is the best available estimate
28	    /// but the global tolerance may not be satisfied.
29	    pub converged: bool,
30	}
31	
32	impl IntegrationOutcome {
33	    /// Convenience accessor returning just the integral estimate.
34	    ///
35	    /// Useful when the caller does not care about diagnostics:
36	    ///
37	    /// ```
38	    /// use quadrature::{integrate, Config};
39	    /// let v = integrate(|x| x, 0.0, 1.0, &Config::new()).unwrap().estimate();
40	    /// assert!((v - 0.5).abs() < 1e-9);
41	    /// ```
42	    pub fn estimate(&self) -> f64 {
43	        self.value
44	    }
45	}
46

1	//! # quadrature
2	//!
3	//! Adaptive numerical integration of one-dimensional definite integrals.
4	//!
5	//! The crate provides a single high-level entry point, [`integrate`], which
6	//! evaluates `∫_a^b f(x) dx` for a finite interval using a globally-adaptive
7	//! Simpson scheme with recursive bisection and local error control. The
8	//! returned [`IntegrationOutcome`] carries both the estimate and diagnostics
9	//! (error estimate, evaluation count, recursion depth, convergence flag).
10	//!
11	//! ```
12	//! use quadrature::{integrate, Config};
13	//! use std::f64::consts::PI;
14	//!
15	//! // ∫_0^pi sin(x) dx = 2
16	//! let out = integrate(f64::sin, 0.0, PI, &Config::new()).unwrap();
17	//! assert!((out.value - 2.0).abs() < 1e-9);
18	//! assert!(out.converged);
19	//! ```
20	//!
21	//! Convergence behaviour is tuned through [`Config`]:
22	//!
23	//! ```
24	//! use quadrature::{integrate, Config};
25	//! let cfg = Config::new().with_tolerance(1e-6).with_max_depth(20);
26	//! let out = integrate(|x| (-x * x).exp(), -3.0, 3.0, &cfg).unwrap();
27	//! assert!(out.value > 1.7 && out.value < 1.8);
28	//! ```
29	//!
30	//! See the [`simpson`] module for a description of the algorithm.
31	
32	#![forbid(unsafe_code)]
33	#![warn(missing_docs)]
34	
35	mod config;
36	mod error;
37	mod outcome;
38	mod simpson;
39	
40	pub use config::{Config, DEFAULT_MAX_DEPTH, DEFAULT_TOLERANCE};
41	pub use error::QuadratureError;
42	pub use outcome::IntegrationOutcome;
43	pub use simpson::integrate;
44	
45	/// Crate-level convenience: integrate with the default [`Config`].
46	///
47	/// Equivalent to `integrate(f, a, b, &Config::new())`.
48	///
49	/// ```
50	/// use quadrature::integrate_default;
51	/// let out = integrate_default(|x| x * x * x, 0.0, 2.0).unwrap();
52	/// // ∫_0^2 x^3 dx = 4
53	/// assert!((out.value - 4.0).abs() < 1e-9);
54	/// ```
55	pub fn integrate_default<F>(f: F, a: f64, b: f64) -> Result<IntegrationOutcome, QuadratureError>
56	where
57	    F: Fn(f64) -> f64,
58	{
59	    integrate(f, a, b, &Config::new())
60	}
61
shell
ls tests/ 2>/dev/null && echo "---" && find . -name '*.rs' -path '*test*'
output
smoke.rs
---
./tests/smoke.rs

[stdout]
smoke.rs
---
./tests/smoke.rs

/workspace/quadrature/tests/smoke.rs

contents
1	//! Trivial smoke test: the public API integrates one smooth function.
2	//! The full behavioral conformance suite is applied separately by the grader.
3	
4	use quadrature::{integrate, integrate_default, Config};
5	
6	#[test]
7	fn integrates_a_smooth_function() {
8	    let out = integrate(|x: f64| x * x, 0.0, 1.0, &Config::new()).unwrap();
9	    assert!((out.value - 1.0 / 3.0).abs() < 1e-9);
10	}
11	
12	#[test]
13	fn integrate_default_runs() {
14	    let out = integrate_default(|x: f64| x, 0.0, 2.0).unwrap();
15	    assert!((out.value - 2.0).abs() < 1e-9);
16	}
17

/workspace/quadrature/src/simpson.rs

− removed
    // TODO(sci-1421): implement the integration driver. Validate the bounds and
    // config, handle the degenerate (a == b) and reversed-bound (a > b) cases,
    // seed the initial panel evaluations, drive `adaptive_simpson_recurse`, and
    // assemble the `IntegrationOutcome`. See the doc comment and the suite.
    let _ = (&f, a, b, config);
    todo!("implement the integrate driver (sci-1421)")
+ added
    config.validate()?;
    if !a.is_finite() {
        return Err(QuadratureError::NonFiniteBound { name: "a", value: a });
    }
    if !b.is_finite() {
        return Err(QuadratureError::NonFiniteBound { name: "b", value: b });
    }

    // Degenerate interval integrates to exactly zero, no work required.
    if a == b {
        return Ok(IntegrationOutcome {
            value: 0.0,
            error_estimate: 0.0,
            evaluations: 0,
            max_depth_reached: 0,
            converged: true,
        });
    }

    // Always integrate over an increasing interval; recover the orientation
    // via the sign convention `∫_a^b = -∫_b^a`.
    let (lo, hi, sign) = if a < b { (a, b, 1.0) } else { (b, a, -1.0) };

    let m = (lo + hi) / 2.0;
    let fa = eval(&f, lo)?;
    let fm = eval(&f, m)?;
    let fb = eval(&f, hi)?;
    let whole = simpson(lo, hi, fa, fm, fb);

    let mut acc = Accumulator {
        evaluations: 3,
        max_depth_reached: 0,
        converged: true,
    };

    let (value, error_estimate) = adaptive_simpson_recurse(
        &f,
        lo,
        hi,
        fa,
        fm,
        fb,
        whole,
        config.tolerance(),
        config.max_depth(),
        0,
        &mut acc,
    )?;

    Ok(IntegrationOutcome {
        value: sign * value,
        error_estimate,
        evaluations: acc.evaluations,
        max_depth_reached: acc.max_depth_reached,
        converged: acc.converged,
    })

/workspace/quadrature/src/simpson.rs

− removed
    // TODO(sci-1421): implement the recursive adaptive Simpson step (see doc).
    let _ = (f, a, b, fa, fm, fb, whole, tol, max_depth, depth, acc);
    todo!("implement adaptive_simpson_recurse (sci-1421)")
+ added
    let m = (a + b) / 2.0;
    let lm = (a + m) / 2.0;
    let rm = (m + b) / 2.0;

    // Exactly two new ordinates per level; the rest are threaded through.
    let flm = eval(f, lm)?;
    acc.evaluations += 1;
    let frm = eval(f, rm)?;
    acc.evaluations += 1;

    let left = simpson(a, m, fa, flm, fm);
    let right = simpson(m, b, fm, frm, fb);
    let refined = left + right;
    // Richardson error proxy: the gap between the refined and whole estimates.
    let delta = refined - whole;

    if acc.max_depth_reached < depth {
        acc.max_depth_reached = depth;
    }

    // Accept when the local error meets tolerance (the |delta| <= 15*tol form),
    // applying the standard /15 Richardson correction to the returned value.
    if delta.abs() <= 15.0 * tol {
        return Ok((refined + delta / 15.0, delta.abs() / 15.0));
    }

    // The depth limit forces acceptance; such a panel is not converged.
    if depth >= max_depth {
        acc.converged = false;
        return Ok((refined + delta / 15.0, delta.abs() / 15.0));
    }

    // Bisect: each half inherits half the local tolerance.
    let (lval, lerr) = adaptive_simpson_recurse(
        f, a, m, fa, flm, fm, left, tol / 2.0, max_depth, depth + 1, acc,
    )?;
    let (rval, rerr) = adaptive_simpson_recurse(
        f, m, b, fm, frm, fb, right, tol / 2.0, max_depth, depth + 1, acc,
    )?;

    Ok((lval + rval, lerr + rerr))
shell
cargo test --release 2>&1 | tail -30
output
Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 7.99s
     Running unittests src/lib.rs (target/release/deps/quadrature-88c215a0312ed4f6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/smoke.rs (target/release/deps/smoke-bdafb16e3950e68e)

running 2 tests
test integrate_default_runs ... ok
test integrates_a_smooth_function ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests quadrature

running 6 tests
test src/config.rs - config::Config (line 17) ... ok
test src/lib.rs - (line 11) ... ok
test src/lib.rs - (line 23) ... ok
test src/outcome.rs - outcome::IntegrationOutcome::estimate (line 37) ... ok
test src/lib.rs - integrate_default (line 49) ... ok
test src/simpson.rs - simpson::integrate (line 81) ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s

[stdout]
   Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 7.99s
     Running unittests src/lib.rs (target/release/deps/quadrature-88c215a0312ed4f6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/smoke.rs (target/release/deps/smoke-bdafb16e3950e68e)

running 2 tests
test integrate_default_runs ... ok
test integrates_a_smooth_function ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests quadrature

running 6 tests
test src/config.rs - config::Config (line 17) ... ok
test src/lib.rs - (line 11) ... ok
test src/lib.rs - (line 23) ... ok
test src/outcome.rs - outcome::IntegrationOutcome::estimate (line 37) ... ok
test src/lib.rs - integrate_default (line 49) ... ok
test src/simpson.rs - simpson::integrate (line 81) ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s
shell
touch src/simpson.rs && cargo build --release 2>&1 | grep -iE 'warning|error' || echo "no warnings"
output
no warnings

[stdout]
no warnings
Done. Both stubbed routines in `src/simpson.rs` are now implemented, the crate builds warning-free under `#![forbid(unsafe_code)]`/`#![warn(missing_docs)]`, and all unit, integration, and doctests pass. What I implemented (no signatures or other modules touched): **`adaptive_simpson_recurse`** , one step of the classic Lyness adaptive Simpson scheme: - Evaluates exactly the two sub-midpoints (`flm`, `frm`) per level via `eval` (propagating `NonFiniteIntegrand` with `?` and bumping `acc.evaluations` by 2); all other ordinates are threaded through. - Forms the two half-panel estimates, takes `delta = (left + right) - whole` as the Richardson proxy. - Accepts when `|delta| <= 15 * tol`, returning `refined + delta/15` with error estimate `|delta|/15`. - On the depth limit, force-accepts and sets `acc.converged = false`. - Otherwise bisects, giving each half `tol/2`, and sums the children's values and errors. - Tracks `acc.max_depth_reached` at the current `depth`. **`integrate`** driver (was also `todo!()`, and the smoke test calls it) , validates config, rejects non-finite bounds, returns `0` for `a == b`, applies the `∫_a^b = -∫_b^a` sign convention so the recursion always sees an increasing interval, seeds the 3 initial evaluations, and assembles the `IntegrationOutcome`. I implemented `integrate` in addition to `adaptive_simpson_recurse` because the file shipped it as a `todo!()` stub too, and it's required for the crate to run; only the function bodies were filled in.
Final diff , what the agent changed

No file changes, the agent left the workspace untouched.

Verifier output , the proof behind the reward
tests/test.sh
[test] injected hidden conformance suite
[test] running cargo test --release (offline)
   Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 0.84s
     Running unittests src/lib.rs (target/release/deps/quadrature-88c215a0312ed4f6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/conformance.rs (target/release/deps/conformance-64dfa41745ece48a)

running 27 tests
test additive_with_many_splits ... ok
test additive_over_adjacent_intervals ... ok
test convenience_default_matches_explicit_config ... ok
test degenerate_interval_is_zero ... ok
test depth_limit_flags_non_convergence ... ok
test error_estimate_bounds_true_error ... ok
test integrates_constant ... ok
test integrates_cubic_exactly ... ok
test integrates_exponential ... ok
test integrates_linear ... ok
test integrates_gaussian_bell ... ok
test integrates_oscillatory ... ok
test integrates_quartic ... ok
test integrates_reciprocal_to_ln ... ok
test integrates_runge_function ... ok
test integrates_sine_over_half_period ... ok
test negative_domain ... ok
test rejects_non_finite_integrand ... ok
test rejects_non_finite_lower_bound ... ok
test rejects_non_finite_upper_bound ... ok
test rejects_non_positive_tolerance ... ok
test rejects_zero_max_depth ... ok
test reports_evaluations_and_depth ... ok
test reversed_bounds_are_exact_negation ... ok
test reversed_bounds_negate_result ... ok
test very_small_interval ... ok
test tighter_tolerance_costs_more_evaluations ... ok

test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/smoke.rs (target/release/deps/smoke-bdafb16e3950e68e)

running 2 tests
test integrates_a_smooth_function ... ok
test integrate_default_runs ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

    Finished `release` profile [optimized] target(s) in 0.01s
   Doc-tests quadrature

running 6 tests
test src/config.rs - config::Config (line 17) ... ok
test src/lib.rs - (line 11) ... ok
test src/lib.rs - (line 23) ... ok
test src/lib.rs - integrate_default (line 49) ... ok
test src/outcome.rs - outcome::IntegrationOutcome::estimate (line 37) ... ok
test src/simpson.rs - simpson::integrate (line 81) ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.32s

[test] all tests passed
[test] reward = 1

Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_6de98564565744b6. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.

Trial trial_6de98564565744b6 · verifier authoritative; classifier explanatory.