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 Implementation
EvidenceTest results show all 27 conformance tests passed, 6 doctests passed, 2 smoke tests passed, zero failures. Agent systematically explored the codebase (config.rs, error.rs, outcome.rs, lib.rs), understood the Lyness adaptive Simpson algorithm, implemented both stubbed functions with correct Richardson correction factor (15.0), proper tolerance halving, depth-based convergence logic, and error handling. Verifier output: 'test result: ok. 27 passed; 0 failed' for conformance suite; reward=1.0. Build completed with no warnings under the specified forbid/warn lints.
Root causeThe agent correctly implemented the adaptive Simpson quadrature algorithm by reading the algorithm specification in the instruction, understanding the supporting module contracts, and implementing both the entry point driver and recursive core with the exact numerical constants and convergence logic specified in the requirements.
RecommendationN/A - task is fine
Trajectory
Tool-by-tool agent trajectory
12 tool calls · 3 tool types · 14 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	//! 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	//! 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	//! # 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	//! 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	//! 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	//! # 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	//! 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	//! 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	//! # 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	//! 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	//! 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	//! # 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: the integral is exactly zero and no work is needed.
    if a == b {
        return Ok(IntegrationOutcome {
            value: 0.0,
            error_estimate: 0.0,
            evaluations: 0,
            max_depth_reached: 0,
            converged: true,
        });
    }

    // Always integrate over the oriented interval `lo < hi`, restoring the sign
    // of a reversed request afterwards: `∫_a^b = -∫_b^a`.
    let (lo, hi, sign) = if a < b { (a, b, 1.0) } else { (b, a, -1.0) };

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

    // Seed the top-level panel: the three ordinates and the coarse estimate.
    let m = 0.5 * (lo + hi);
    let fa = eval(&f, lo)?;
    let fm = eval(&f, m)?;
    let fb = eval(&f, hi)?;
    acc.evaluations += 3;
    let whole = simpson(lo, hi, fa, fm, fb);

    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
    // Refine: split `[a, b]` at its midpoint and evaluate the two new
    // sub-midpoints. These are the only fresh evaluations at this level.
    let m = 0.5 * (a + b);
    let lm = 0.5 * (a + m);
    let rm = 0.5 * (m + b);
    let flm = eval(f, lm)?;
    let frm = eval(f, rm)?;
    acc.evaluations += 2;

    let left = simpson(a, m, fa, flm, fm);
    let right = simpson(m, b, fm, frm, fb);

    // Richardson error proxy: refined estimate minus the coarse one.
    let delta = left + right - whole;
    let refined = left + right + delta / 15.0;
    let error = (delta / 15.0).abs();

    if delta.abs() <= 15.0 * tol {
        // Local tolerance met: accept with the Richardson correction.
        acc.max_depth_reached = acc.max_depth_reached.max(depth);
        Ok((refined, error))
    } else if depth >= max_depth {
        // Out of depth before the tolerance was met: accept the best estimate
        // available, but flag the run as not converged.
        acc.converged = false;
        acc.max_depth_reached = acc.max_depth_reached.max(depth);
        Ok((refined, error))
    } else {
        // Bisect, splitting the local tolerance evenly between the halves.
        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.36s
     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 - (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.36s

[stdout]
   Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 1.36s
     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 - (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.36s
shell
cargo build --release 2>&1 | tail -5; echo "=== clippy ==="; cargo clippy --release 2>&1 | tail -15
output
Finished `release` profile [optimized] target(s) in 0.01s
=== clippy ===
error: 'cargo-clippy' is not installed for the toolchain '1.83.0-x86_64-unknown-linux-gnu'.
To install, run `rustup component add clippy`

[stdout]
    Finished `release` profile [optimized] target(s) in 0.01s
=== clippy ===
error: 'cargo-clippy' is not installed for the toolchain '1.83.0-x86_64-unknown-linux-gnu'.
To install, run `rustup component add clippy`
shell
touch src/simpson.rs && cargo build --release 2>&1
output
Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 0.07s

[stdout]
   Compiling quadrature v0.3.1 (/workspace/quadrature)
    Finished `release` profile [optimized] target(s) in 0.07s
Done. Clean build with no warnings; all unit, integration, and doctests pass. I implemented the two stubbed routines in `src/simpson.rs`, reusing the provided `eval` and `simpson` helpers and matching the standard Lyness adaptive-Simpson constants described in the module docs: **`integrate` (driver):** - Validates config (`config.validate()?`) and checks both bounds are finite, else `NonFiniteBound`. - Degenerate `a == b` → exactly `0` with no evaluations. - Orients to `lo < hi`, recording a `sign` so reversed bounds give `∫_a^b = -∫_b^a` exactly. - Seeds the three top-level ordinates (3 evaluations) and the coarse Simpson estimate, then drives the recursion and assembles the `IntegrationOutcome`. **`adaptive_simpson_recurse` (core):** - Evaluates exactly the two new sub-midpoints per level (2 evals), threading the other ordinates through unrecomputed. - Local error proxy `delta = left + right - whole`; Richardson-corrected value `left + right + delta/15`, error estimate `|delta|/15`. - Acceptance threshold `|delta| <= 15·tol`; on bisection the tolerance is halved per sub-panel and depth increments. - Tolerance check precedes the depth check, so a panel accepted *only* because `depth >= max_depth` sets `converged = false`; `max_depth_reached` tracks the deepest accepted level. This satisfies the contract points: accuracy (Richardson correction), orientation/degenerate handling, adaptive diagnostics, monotonic eval count vs. tolerance, conservative summed error estimate, the convergence flag under shallow depth, additivity (sums of sub-panel results), and error propagation via `?` from `eval`.
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.75s
     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.34s

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

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

Trial trial_ed14b03551054081 · verifier authoritative; classifier explanatory.