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 35 tests pass: 27 conformance tests (covering accuracy, orientation, edge cases, error handling, diagnostics, and structural invariants) + 2 smoke tests + 6 doctests. The implementation correctly: (1) validates non-finite bounds and config; (2) handles degenerate intervals (a==b) returning 0.0; (3) reverses bounds and restores sign for reversed intervals; (4) implements classic Lyness adaptive Simpson with Richardson error correction (factor 1/15); (5) performs exactly 2 new integrand evaluations per recursion level; (6) apportions tolerance correctly; (7) tracks evaluations, max_depth_reached, and converged flag accurately.
Root causeAgent successfully understood the detailed adaptive Simpson algorithm specification and implemented both the integration driver and recursive core correctly, passing comprehensive behavioral test suite covering accuracy, edge cases, error handling, and algorithm invariants.
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
    if !a.is_finite() {
        return Err(QuadratureError::NonFiniteBound {
            name: "a",
            value: a,
        });
    }
    if !b.is_finite() {
        return Err(QuadratureError::NonFiniteBound {
            name: "b",
            value: b,
        });
    }
    config.validate()?;

    // Degenerate interval: the integral is exactly zero and costs no work.
    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 and restore the sign at the
    // end: `∫_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 flo = eval(&f, lo)?;
    let fm = eval(&f, m)?;
    let fhi = eval(&f, hi)?;
    let whole = simpson(lo, hi, flo, fm, fhi);

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

    let (value, error_estimate) = adaptive_simpson_recurse(
        &f,
        lo,
        hi,
        flo,
        fm,
        fhi,
        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
    // Record the deepest level reached so far.
    acc.max_depth_reached = acc.max_depth_reached.max(depth);

    let m = (a + b) / 2.0;
    let lm = (a + m) / 2.0;
    let rm = (m + b) / 2.0;

    // The only two new evaluations performed at this level.
    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;
    let diff = refined - whole;

    // Standard adaptive-Simpson accept test: the refined and whole estimates
    // differ to fourth order, so `|diff| <= 15 * tol` implies the corrected
    // estimate is within `tol`. The `/15` Richardson term cancels the leading
    // error of `refined`, and `|diff| / 15` is the matching error bound.
    if diff.abs() <= 15.0 * tol {
        return Ok((refined + diff / 15.0, diff.abs() / 15.0));
    }

    // Out of tolerance: bisect unless we have hit the depth limit, in which
    // case we are forced to accept the best estimate and flag non-convergence.
    if depth >= max_depth {
        acc.converged = false;
        return Ok((refined + diff / 15.0, diff.abs() / 15.0));
    }

    // Recurse on each half, splitting the local tolerance budget evenly so the
    // sum of the children's tolerances equals this panel's.
    let (lv, le) = adaptive_simpson_recurse(
        f,
        a,
        m,
        fa,
        flm,
        fm,
        left,
        tol / 2.0,
        max_depth,
        depth + 1,
        acc,
    )?;
    let (rv, re) = adaptive_simpson_recurse(
        f,
        m,
        b,
        fm,
        frm,
        fb,
        right,
        tol / 2.0,
        max_depth,
        depth + 1,
        acc,
    )?;

    Ok((lv + rv, le + re))
shell
cargo test --release 2>&1 | tail -40
output
Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 1.29s
     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 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

   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 - integrate_default (line 49) ... ok
test src/lib.rs - (line 23) ... 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.38s

[stdout]
   Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 1.29s
     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 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

   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 - integrate_default (line 49) ... ok
test src/lib.rs - (line 23) ... 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.38s
shell
touch src/simpson.rs && cargo build --release 2>&1 | grep -i warn || echo "no warnings"
output
no warnings

[stdout]
no warnings
Done. Both stubbed routines in `src/simpson.rs` were `todo!()`, so I implemented both to make the suite pass without touching any signature or other module. **`adaptive_simpson_recurse`** , one classic adaptive-Simpson step: - Records `max_depth_reached`, evaluates the two sub-midpoints (`eval` with `?` for error propagation), incrementing `evaluations` by exactly 2 per level. - Forms the two half-panel Simpson estimates; `diff = (left + right) − whole` is the local error proxy. - **Accepts** when `|diff| ≤ 15·tol`, returning the Richardson-corrected value `refined + diff/15` and error `|diff|/15`. - Otherwise **bisects**, splitting `tol` in half per child (so children's budgets sum to the parent's) and threading the shared ordinates through , no recomputation. - At `depth ≥ max_depth` it force-accepts and sets `converged = false`. **`integrate`** , the driver: validates finite bounds and config, returns `0` for `a == b`, normalizes to `a < b` with a sign flip (`∫_a^b = −∫_b^a`), seeds the 3 initial ordinates/`whole`, runs the recursion from `depth 0`, and assembles the `IntegrationOutcome`. Results: all unit, smoke, and doctest cases pass under `cargo test --release`, with a clean warning-free build.
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.66s
     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_over_adjacent_intervals ... ok
test additive_with_many_splits ... 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_gaussian_bell ... ok
test integrates_linear ... ok
test integrates_oscillatory ... ok
test integrates_quartic ... ok
test integrates_reciprocal_to_ln ... ok
test integrates_runge_function ... ok
test negative_domain ... ok
test integrates_sine_over_half_period ... 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 tighter_tolerance_costs_more_evaluations ... ok
test very_small_interval ... 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.33s

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

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

Trial trial_81c27a0629c040eb · verifier authoritative; classifier explanatory.