use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticError {
NonFiniteBound,
InvalidOrder,
DomainOverflow,
NonFiniteEvaluation,
}
impl fmt::Display for DiagnosticError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonFiniteBound => formatter.write_str("diagnostic bounds must be finite"),
Self::InvalidOrder => {
formatter.write_str("diagnostic lower bound must be below upper bound")
}
Self::DomainOverflow => formatter.write_str("diagnostic domain overflowed"),
Self::NonFiniteEvaluation => {
formatter.write_str("diagnostic evaluation was non-finite")
}
}
}
}
impl std::error::Error for DiagnosticError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArbitrageStatus {
ViolationDetected,
NoViolationDetected,
Indeterminate,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScanConfig {
lower: f64,
upper: f64,
points: usize,
tolerance: f64,
}
impl ScanConfig {
#[must_use]
pub fn new(lower: f64, upper: f64, points: usize, tolerance: f64) -> Option<Self> {
if lower.is_finite()
&& upper.is_finite()
&& lower < upper
&& points >= 3
&& tolerance.is_finite()
&& tolerance >= 0.0
{
Some(Self {
lower,
upper,
points,
tolerance,
})
} else {
None
}
}
#[must_use]
pub const fn lower(self) -> f64 {
self.lower
}
#[must_use]
pub const fn upper(self) -> f64 {
self.upper
}
#[must_use]
pub const fn points(self) -> usize {
self.points
}
#[must_use]
pub const fn tolerance(self) -> f64 {
self.tolerance
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScanEvidence {
config: ScanConfig,
requested_lower: f64,
requested_upper: f64,
requested_points: usize,
samples_evaluated: usize,
scan_count: usize,
total_samples_evaluated: usize,
selected_scan: usize,
refinement_attempted: bool,
refinement: Option<RootEvidence>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RawSearchStage {
Domain,
Wings,
FukasawaInterval,
Location,
G2Roots,
SigmaSearch,
Complete,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RootEvidence {
root: f64,
lower: f64,
upper: f64,
residual: f64,
evaluations: usize,
termination: RootTermination,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RootTermination {
ExactRoot,
BracketTolerance,
}
impl RootEvidence {
pub(crate) const fn new(
root: f64,
lower: f64,
upper: f64,
residual: f64,
evaluations: usize,
termination: RootTermination,
) -> Self {
Self {
root,
lower,
upper,
residual,
evaluations,
termination,
}
}
#[must_use]
pub const fn root(self) -> f64 {
self.root
}
#[must_use]
pub const fn lower(self) -> f64 {
self.lower
}
#[must_use]
pub const fn upper(self) -> f64 {
self.upper
}
#[must_use]
pub const fn residual(self) -> f64 {
self.residual
}
#[must_use]
pub const fn evaluations(self) -> usize {
self.evaluations
}
#[must_use]
pub const fn termination(self) -> RootTermination {
self.termination
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SearchEvidence {
algorithm: &'static str,
stage: RawSearchStage,
compact_domains: [(f64, f64); 2],
absolute_tolerance: f64,
relative_tolerance: f64,
optimization_error: f64,
evaluations: usize,
subdivisions: usize,
roots: [Option<RootEvidence>; 4],
fukasawa_interval: Option<(f64, f64)>,
sigma_star: Option<f64>,
argmax_ell: Option<f64>,
terminated: bool,
}
impl SearchEvidence {
#[allow(clippy::too_many_arguments)]
pub(crate) const fn new(
algorithm: &'static str,
stage: RawSearchStage,
compact_domains: [(f64, f64); 2],
absolute_tolerance: f64,
relative_tolerance: f64,
optimization_error: f64,
evaluations: usize,
subdivisions: usize,
roots: [Option<RootEvidence>; 4],
fukasawa_interval: Option<(f64, f64)>,
sigma_star: Option<f64>,
argmax_ell: Option<f64>,
terminated: bool,
) -> Self {
Self {
algorithm,
stage,
compact_domains,
absolute_tolerance,
relative_tolerance,
optimization_error,
evaluations,
subdivisions,
roots,
fukasawa_interval,
sigma_star,
argmax_ell,
terminated,
}
}
#[must_use]
pub const fn algorithm(self) -> &'static str {
self.algorithm
}
#[must_use]
pub const fn stage(self) -> RawSearchStage {
self.stage
}
#[must_use]
pub const fn compact_domains(self) -> [(f64, f64); 2] {
self.compact_domains
}
#[must_use]
pub const fn absolute_tolerance(self) -> f64 {
self.absolute_tolerance
}
#[must_use]
pub const fn relative_tolerance(self) -> f64 {
self.relative_tolerance
}
#[must_use]
pub const fn optimization_error(self) -> f64 {
self.optimization_error
}
#[must_use]
pub const fn evaluations(self) -> usize {
self.evaluations
}
#[must_use]
pub const fn subdivisions(self) -> usize {
self.subdivisions
}
#[must_use]
pub const fn roots(self) -> [Option<RootEvidence>; 4] {
self.roots
}
#[must_use]
pub const fn fukasawa_interval(self) -> Option<(f64, f64)> {
self.fukasawa_interval
}
#[must_use]
pub const fn sigma_star(self) -> Option<f64> {
self.sigma_star
}
#[must_use]
pub const fn argmax_ell(self) -> Option<f64> {
self.argmax_ell
}
#[must_use]
pub const fn terminated(self) -> bool {
self.terminated
}
}
impl ScanEvidence {
pub(crate) const fn new(
config: ScanConfig,
requested_domain: (f64, f64),
requested_points: usize,
samples_evaluated: usize,
refinement_attempted: bool,
refinement: Option<RootEvidence>,
) -> Self {
Self {
config,
requested_lower: requested_domain.0,
requested_upper: requested_domain.1,
requested_points,
samples_evaluated,
scan_count: 1,
total_samples_evaluated: samples_evaluated,
selected_scan: 0,
refinement_attempted,
refinement,
}
}
pub(crate) const fn aggregate_selected(
selected: Self,
scan_count: usize,
total_samples_evaluated: usize,
selected_scan: usize,
) -> Self {
Self {
scan_count,
total_samples_evaluated,
selected_scan,
..selected
}
}
#[must_use]
pub const fn requested_domain(self) -> (f64, f64) {
(self.requested_lower, self.requested_upper)
}
#[must_use]
pub const fn requested_points(self) -> usize {
self.requested_points
}
#[must_use]
pub const fn config(self) -> ScanConfig {
self.config
}
#[must_use]
pub const fn samples_evaluated(self) -> usize {
self.samples_evaluated
}
#[must_use]
pub const fn scan_count(self) -> usize {
self.scan_count
}
#[must_use]
pub const fn total_samples_evaluated(self) -> usize {
self.total_samples_evaluated
}
#[must_use]
pub const fn selected_scan(self) -> usize {
self.selected_scan
}
#[must_use]
pub const fn refinement_attempted(self) -> bool {
self.refinement_attempted
}
#[must_use]
pub const fn refinement(self) -> Option<RootEvidence> {
self.refinement
}
}
#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum ArbitrageEvidence {
AnalyticNecessaryAndSufficient {
theorem: &'static str,
boundary_tolerance: f64,
},
AnalyticSufficient {
theorem: &'static str,
},
AnalyticNecessary {
condition: &'static str,
},
NumericalScan(ScanEvidence),
NumericalSearch(SearchEvidence),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ArbitrageAssessment {
status: ArbitrageStatus,
evidence: ArbitrageEvidence,
margin: f64,
witness: Option<f64>,
}
impl ArbitrageAssessment {
#[allow(clippy::large_types_passed_by_value)] pub(crate) const fn new(
status: ArbitrageStatus,
evidence: ArbitrageEvidence,
margin: f64,
witness: Option<f64>,
) -> Self {
Self {
status,
evidence,
margin,
witness,
}
}
#[must_use]
pub const fn status(self) -> ArbitrageStatus {
self.status
}
#[must_use]
pub const fn evidence(self) -> ArbitrageEvidence {
self.evidence
}
#[must_use]
pub const fn margin(self) -> f64 {
self.margin
}
#[must_use]
pub const fn witness(self) -> Option<f64> {
self.witness
}
}