use std::collections::BTreeMap;
use crate::monotone_proof::ProofLimits;
use crate::{
CoverageFence, CoverageLimits, Error, KineticEdge, KineticEdgeKey, KineticFiltration,
KineticLimits, PlanarCoverageModel, Result, SparseDistanceMatrix,
};
use super::super::evaluate::{find_set, union_sets, validate_actions};
use super::types::{CoverageAction, CoverageComponent};
pub(crate) const FORMAT_MAX_PROOF_NODES: usize = 10_000_000;
pub(crate) const FORMAT_MAX_PROOF_TERMS: usize = 100_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct CoverageSynthesisLimits {
pub max_bytes: usize,
pub max_oracle_calls: usize,
pub max_search_nodes: usize,
pub max_proof_nodes: usize,
pub max_proof_depth: usize,
pub max_proof_terms: usize,
pub coverage: CoverageLimits,
pub kinetic: KineticLimits,
}
impl Default for CoverageSynthesisLimits {
fn default() -> Self {
Self {
max_bytes: 1 << 30,
max_oracle_calls: 2_000_000,
max_search_nodes: 2_000_000,
max_proof_nodes: 2_000_000,
max_proof_depth: 1_024,
max_proof_terms: 10_000_000,
coverage: CoverageLimits::default(),
kinetic: KineticLimits::default(),
}
}
}
impl CoverageSynthesisLimits {
#[must_use]
pub fn with_max_oracle_calls(mut self, maximum: usize) -> Self {
self.max_oracle_calls = maximum;
self
}
#[must_use]
pub fn with_max_search_nodes(mut self, maximum: usize) -> Self {
self.max_search_nodes = maximum;
self
}
pub(crate) fn proof(self) -> ProofLimits {
ProofLimits {
nodes: self.max_proof_nodes.min(FORMAT_MAX_PROOF_NODES),
depth: self.max_proof_depth,
terms: self.max_proof_terms.min(FORMAT_MAX_PROOF_TERMS),
checks: self.max_oracle_calls,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CoverageSource {
Finite,
Affine {
scenario: u64,
edges: Vec<KineticEdge>,
start: f64,
end: f64,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoverageState {
pub(crate) scenario: u64,
pub(crate) step: u64,
pub(crate) base_vertices: Vec<usize>,
pub(crate) possible_edges: Vec<KineticEdgeKey>,
}
impl CoverageState {
pub fn new(
scenario: u64,
step: u64,
graph: &SparseDistanceMatrix,
mut base_vertices: Vec<usize>,
broadcast_radius: f64,
) -> Result<Self> {
base_vertices.sort_unstable();
base_vertices.dedup();
if !broadcast_radius.is_finite()
|| broadcast_radius <= 0.0
|| base_vertices.iter().any(|vertex| *vertex >= graph.len())
{
return Err(Error::InvalidInput(
"coverage state has an invalid radius or base vertex".into(),
));
}
Ok(Self {
scenario,
step,
base_vertices,
possible_edges: graph
.edges()
.filter(|edge| edge.2 <= broadcast_radius)
.map(|(u, v, _)| KineticEdgeKey::new(u, v))
.collect(),
})
}
pub fn scenario(&self) -> u64 {
self.scenario
}
pub fn step(&self) -> u64 {
self.step
}
pub fn base_vertices(&self) -> &[usize] {
&self.base_vertices
}
pub fn possible_edges(&self) -> &[KineticEdgeKey] {
&self.possible_edges
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CoverageSpecification {
pub(crate) vertex_count: usize,
pub(crate) model: PlanarCoverageModel,
pub(crate) modulus: u32,
pub(crate) fence: CoverageFence,
pub(crate) failable_vertices: Vec<usize>,
pub(crate) failure_budget: usize,
pub(crate) source: CoverageSource,
pub(crate) states: Vec<CoverageState>,
}
impl CoverageSpecification {
#[allow(clippy::too_many_arguments)]
pub fn new(
vertex_count: usize,
model: PlanarCoverageModel,
modulus: u32,
fence: CoverageFence,
mut failable_vertices: Vec<usize>,
failure_budget: usize,
mut states: Vec<CoverageState>,
limits: CoverageLimits,
) -> Result<Self> {
failable_vertices.sort_unstable();
failable_vertices.dedup();
states.sort_by_key(|state| (state.scenario, state.step));
let specification = Self {
vertex_count,
model,
modulus,
fence,
failable_vertices,
failure_budget,
source: CoverageSource::Finite,
states,
};
specification.validate(limits)?;
Ok(specification)
}
#[allow(clippy::too_many_arguments)]
pub fn from_kinetic(
filtration: &KineticFiltration,
scenario: u64,
model: PlanarCoverageModel,
modulus: u32,
fence: CoverageFence,
failable_vertices: Vec<usize>,
failure_budget: usize,
base_vertices: Vec<usize>,
limits: CoverageLimits,
) -> Result<Self> {
let states = filtration
.critical_graphs(model.broadcast_radius())?
.into_iter()
.enumerate()
.map(|(step, state)| {
CoverageState::new(
scenario,
step as u64,
&state.graph,
base_vertices.clone(),
model.broadcast_radius(),
)
})
.collect::<Result<Vec<_>>>()?;
let mut specification = Self::new(
filtration.vertex_count(),
model,
modulus,
fence,
failable_vertices,
failure_budget,
states,
limits,
)?;
specification.source = CoverageSource::Affine {
scenario,
edges: filtration.edges().to_vec(),
start: filtration.start(),
end: filtration.end(),
};
Ok(specification)
}
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
pub fn model(&self) -> PlanarCoverageModel {
self.model
}
pub fn modulus(&self) -> u32 {
self.modulus
}
pub fn fence(&self) -> &CoverageFence {
&self.fence
}
pub fn failable_vertices(&self) -> &[usize] {
&self.failable_vertices
}
pub fn failure_budget(&self) -> usize {
self.failure_budget
}
pub fn source(&self) -> &CoverageSource {
&self.source
}
pub fn states(&self) -> &[CoverageState] {
&self.states
}
pub(crate) fn validate(&self, limits: CoverageLimits) -> Result<()> {
PlanarCoverageModel::new(self.model.broadcast_radius(), self.model.sensing_radius())?;
self.validate_scope(limits)?;
if self
.states
.windows(2)
.any(|pair| (pair[0].scenario, pair[0].step) >= (pair[1].scenario, pair[1].step))
{
return Err(Error::InvalidInput(
"coverage states are not in canonical scenario and step order".into(),
));
}
for state in &self.states {
self.validate_state(state, limits)?;
}
Ok(())
}
pub(super) fn validate_scope(&self, limits: CoverageLimits) -> Result<()> {
let invalid_failable = self
.failable_vertices
.iter()
.any(|vertex| *vertex >= self.vertex_count);
let failable_fence = self
.fence
.vertices()
.iter()
.any(|vertex| self.failable_vertices.binary_search(vertex).is_ok());
if self.vertex_count == 0
|| self.vertex_count > limits.max_vertices
|| self.states.is_empty()
|| self.states.len() > limits.max_states
|| self
.fence
.vertices()
.iter()
.any(|vertex| *vertex >= self.vertex_count)
|| invalid_failable
|| failable_fence
|| self.failure_budget > self.failable_vertices.len()
{
Err(Error::InvalidInput(
"coverage specification has an invalid scope, fence, or failure model".into(),
))
} else {
Ok(())
}
}
pub(super) fn validate_state(
&self,
state: &CoverageState,
limits: CoverageLimits,
) -> Result<()> {
let invalid_edge = state
.possible_edges
.iter()
.any(|edge| edge.u >= edge.v || edge.v >= self.vertex_count);
let missing_fence = self
.fence
.vertices()
.iter()
.any(|vertex| state.base_vertices.binary_search(vertex).is_err());
if state
.base_vertices
.iter()
.any(|vertex| *vertex >= self.vertex_count)
|| state.possible_edges.len() > limits.max_edges
|| invalid_edge
|| state
.possible_edges
.windows(2)
.any(|pair| pair[0] >= pair[1])
|| missing_fence
{
Err(Error::InvalidInput(
"coverage state is not canonical or omits a fence vertex".into(),
))
} else {
Ok(())
}
}
pub fn components(&self, actions: &[CoverageAction]) -> Result<Vec<CoverageComponent>> {
validate_actions(self, actions, CoverageLimits::default())?;
let offset = self.states.len();
let mut parent = (0..offset + actions.len()).collect::<Vec<_>>();
for (action, candidate) in actions.iter().enumerate() {
for &state in &candidate.states {
union_sets(&mut parent, state, offset + action);
}
}
let mut components = BTreeMap::<usize, CoverageComponent>::new();
for state in 0..self.states.len() {
let root = find_set(&mut parent, state);
components.entry(root).or_default().states.push(state);
}
for action in 0..actions.len() {
let root = find_set(&mut parent, offset + action);
components.entry(root).or_default().actions.push(action);
}
let mut output = components.into_values().collect::<Vec<_>>();
output.sort_by_key(|component| {
(
component.states.first().copied().unwrap_or(usize::MAX),
component.actions.first().copied().unwrap_or(usize::MAX),
)
});
Ok(output)
}
}