use std::cmp::Ordering;
use thiserror::Error;
use crate::supply::SupplyBound;
use crate::time::{Duration, Offset, Service};
#[cfg(debug_assertions)]
use crate::time::Time;
#[derive(Debug, Error, Copy, Clone, Eq, PartialEq, PartialOrd)]
pub enum SearchFailure {
#[error("no fixed point less than {limit} found for offset {offset}")]
DivergenceLimitExceeded { offset: Offset, limit: Duration },
#[error("analysis assumption violated")]
AssumptionViolated,
}
pub type SearchResult = Result<Duration, SearchFailure>;
pub fn search_with_offset<SBF, RHS>(
supply: &SBF,
offset: Offset,
divergence_limit: Duration,
workload: &RHS,
) -> SearchResult
where
SBF: SupplyBound + ?Sized,
RHS: Fn(Duration) -> Service,
{
let mut assumed_response_time = Duration::from(1);
while assumed_response_time <= divergence_limit {
let demand = workload(assumed_response_time);
let demand_met = Offset::from_time_zero(supply.service_time(demand));
let response_time_bound = offset.distance_to(demand_met);
if response_time_bound <= assumed_response_time {
return Ok(response_time_bound);
} else {
assumed_response_time = response_time_bound
}
}
Err(SearchFailure::DivergenceLimitExceeded {
offset,
limit: divergence_limit,
})
}
#[cfg(debug_assertions)]
fn brute_force_search_with_offset<SBF, RHS>(
supply: &SBF,
offset: Offset,
divergence_limit: Duration,
workload: &RHS,
) -> SearchResult
where
SBF: SupplyBound + ?Sized,
RHS: Fn(Duration) -> Service,
{
for r in 1..=Time::from(divergence_limit) {
let assumed_response_time = Duration::from(r);
let lhs = supply.provided_service(offset.since_time_zero() + assumed_response_time);
let rhs = workload(assumed_response_time);
if rhs.is_none() {
return Ok(Duration::zero());
} else if lhs == rhs {
return Ok(assumed_response_time);
}
}
Err(SearchFailure::DivergenceLimitExceeded {
offset,
limit: divergence_limit,
})
}
pub fn search<SBF, RHS>(
supply: &SBF,
divergence_limit: Duration,
workload_bound: RHS,
) -> SearchResult
where
SBF: SupplyBound + ?Sized,
RHS: Fn(Duration) -> Service,
{
let bw = search_with_offset(supply, Offset::from(0), divergence_limit, &workload_bound);
#[cfg(debug_assertions)]
if divergence_limit <= Duration::from(100_000) {
debug_assert_eq!(
brute_force_search_with_offset(
supply,
Offset::from(0),
divergence_limit,
&workload_bound
),
bw
);
}
bw
}
pub fn max_response_time(rta_per_offset: impl Iterator<Item = SearchResult>) -> SearchResult {
rta_per_offset
.max_by(|a, b| {
if a.is_err() {
Ordering::Greater
} else if b.is_err() {
Ordering::Less
} else {
a.unwrap().cmp(&b.unwrap())
}
})
.unwrap_or(Ok(Duration::zero()))
}