use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSeverity {
Info,
Warning,
Degraded,
Error,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelStage {
Collect,
Classify,
Intersect,
Refine,
Fragment,
Select,
Sew,
Validate,
Export,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DiagnosticEvent {
pub severity: DiagnosticSeverity,
pub stage: KernelStage,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct KernelDiagnostics {
pub events: Vec<DiagnosticEvent>,
pub counters: BTreeMap<String, u64>,
pub measurements: BTreeMap<String, f64>,
}
impl KernelDiagnostics {
pub fn event(
&mut self,
severity: DiagnosticSeverity,
stage: KernelStage,
code: impl Into<String>,
message: impl Into<String>,
) {
self.events.push(DiagnosticEvent {
severity,
stage,
code: code.into(),
message: message.into(),
});
}
pub fn count(&mut self, code: impl Into<String>) {
*self.counters.entry(code.into()).or_default() += 1;
}
pub fn count_n(&mut self, code: impl Into<String>, amount: u64) {
*self.counters.entry(code.into()).or_default() += amount;
}
pub fn measure_max(&mut self, code: impl Into<String>, value: f64) {
let entry = self.measurements.entry(code.into()).or_insert(value);
*entry = entry.max(value);
}
pub fn worst(&self) -> Option<DiagnosticSeverity> {
self.events.iter().map(|event| event.severity).max()
}
pub fn shippable(&self) -> bool {
self.events
.iter()
.all(|event| event.severity < DiagnosticSeverity::Error)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct KernelRefusal {
#[serde(flatten)]
pub class: RefusalClass,
pub stage: KernelStage,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "class", rename_all = "snake_case")]
pub enum RefusalClass {
DegenerateArrangement { open_edges: u32, issues: u32 },
NonIntegralGenus { shells: u32, euler: i64 },
NonPositiveVolume,
TangentNodeSingularity,
InvalidResultTopology { issues: u32 },
ConservativeEmptyOverlap,
NonConvergence { what: String },
UnsupportedGeometry { what: String },
InvalidInput { what: String },
IllPosed { what: String },
UnsoundResult {
defect: SoundnessDefect,
faces: Vec<u64>,
},
Internal { what: String },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Backing {
Refuted,
Undecided { cause: UndecidedCause },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UndecidedCause {
Degeneracy,
Contact,
Budget,
Defect,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SoundnessDefect {
Fold,
Crossing,
HoleBreach,
VectorArea,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Approximation {
pub code: String,
pub body: String,
pub measured: f64,
pub bar: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub volume_bound: Option<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edges: Vec<OffCarrierEdge>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget: Option<ApproximationBudget>,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ApproximationBudget {
pub reason: BudgetReason,
pub rounds_used: usize,
pub rounds_limit: usize,
pub stations: usize,
pub station_limit: usize,
#[serde(default)]
pub mechanism: BudgetMechanism,
#[serde(default)]
pub measured_component: MeasuredComponent,
#[serde(default, skip_serializing_if = "is_zero")]
pub unread: usize,
}
fn is_zero(count: &usize) -> bool {
*count == 0
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MeasuredComponent {
#[default]
Wall,
Rails,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BudgetMechanism {
#[default]
LocalRounds,
StationRungs,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BudgetReason {
RoundsSpent,
StationCeiling,
Incoherent,
Rejected,
Unaccepted,
Unread,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct OffCarrierEdge {
pub edge_id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub curve_ref: Option<usize>,
pub face_ref: usize,
pub surface_ref: usize,
pub off_carrier_mm: f64,
pub origin: EdgeOrigin,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EdgeOrigin {
File,
Reconciled,
}
impl RefusalClass {
pub fn backing(&self) -> Backing {
use UndecidedCause::*;
match self {
Self::DegenerateArrangement { .. }
| Self::NonIntegralGenus { .. }
| Self::NonPositiveVolume
| Self::TangentNodeSingularity
| Self::InvalidResultTopology { .. } => Backing::Undecided { cause: Degeneracy },
Self::ConservativeEmptyOverlap => Backing::Undecided { cause: Contact },
Self::NonConvergence { .. } => Backing::Undecided { cause: Budget },
Self::Internal { .. } | Self::UnsoundResult { .. } => {
Backing::Undecided { cause: Defect }
}
Self::UnsupportedGeometry { .. }
| Self::InvalidInput { .. }
| Self::IllPosed { .. } => Backing::Refuted,
}
}
pub fn perturbation_eligible(&self) -> bool {
matches!(
self.backing(),
Backing::Undecided {
cause: UndecidedCause::Degeneracy
}
)
}
pub fn tag(&self) -> &'static str {
match self {
Self::DegenerateArrangement { .. } => "degenerate_arrangement",
Self::NonIntegralGenus { .. } => "non_integral_genus",
Self::NonPositiveVolume => "non_positive_volume",
Self::TangentNodeSingularity => "tangent_node_singularity",
Self::InvalidResultTopology { .. } => "invalid_result_topology",
Self::ConservativeEmptyOverlap => "conservative_empty_overlap",
Self::NonConvergence { .. } => "non_convergence",
Self::UnsupportedGeometry { .. } => "unsupported_geometry",
Self::InvalidInput { .. } => "invalid_input",
Self::IllPosed { .. } => "ill_posed",
Self::UnsoundResult { .. } => "unsound_result",
Self::Internal { .. } => "internal",
}
}
}
impl KernelRefusal {
pub fn new(class: RefusalClass, stage: KernelStage, message: impl Into<String>) -> Self {
Self {
class,
stage,
message: message.into(),
}
}
pub fn internal(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(RefusalClass::Internal { what: what.into() }, stage, message)
}
pub fn input(stage: KernelStage, what: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(
RefusalClass::InvalidInput { what: what.into() },
stage,
message,
)
}
pub fn unsupported(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(
RefusalClass::UnsupportedGeometry { what: what.into() },
stage,
message,
)
}
pub fn ill_posed(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(RefusalClass::IllPosed { what: what.into() }, stage, message)
}
pub fn unsound(
stage: KernelStage,
defect: SoundnessDefect,
faces: Vec<u64>,
message: impl Into<String>,
) -> Self {
Self::new(RefusalClass::UnsoundResult { defect, faces }, stage, message)
}
pub fn non_convergence(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(
RefusalClass::NonConvergence { what: what.into() },
stage,
message,
)
}
pub fn with_message(mut self, f: impl FnOnce(&str) -> String) -> Self {
self.message = f(&self.message);
self
}
}
impl std::fmt::Display for KernelRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for KernelRefusal {}
impl From<KernelRefusal> for String {
fn from(refusal: KernelRefusal) -> Self {
refusal.message
}
}
pub trait OrRefuse<T> {
fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
}
impl<T> OrRefuse<T> for Result<T, String> {
fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
self.map_err(|message| KernelRefusal::internal(stage, what, message))
}
fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
self.map_err(|message| KernelRefusal::input(stage, what, message))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KernelOutcome<T> {
pub value: T,
pub diagnostics: KernelDiagnostics,
}