use serde::{Deserialize, Serialize};
use super::error::TetError;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AxisSlice {
pub start: Option<u64>,
pub stop: Option<u64>,
pub step: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start_label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_label: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Operation {
Sum {
axes: Vec<String>,
},
Mean {
axes: Vec<String>,
},
Min {
axes: Vec<String>,
},
Max {
axes: Vec<String>,
},
Count {
axes: Vec<String>,
},
Var {
axes: Vec<String>,
},
Std {
axes: Vec<String>,
},
Product {
axes: Vec<String>,
},
NormL1 {
axes: Vec<String>,
},
NormL2 {
axes: Vec<String>,
},
AllFinite {
axes: Vec<String>,
},
AnyNan {
axes: Vec<String>,
},
AnyInf {
axes: Vec<String>,
},
ArgMin {
axes: Vec<String>,
},
ArgMax {
axes: Vec<String>,
},
Median {
axes: Vec<String>,
},
Quantile {
axes: Vec<String>,
q: f64,
},
Histogram {
axes: Vec<String>,
bins: u32,
min: Option<f64>,
max: Option<f64>,
},
NanCount {
axes: Vec<String>,
},
InfCount {
axes: Vec<String>,
},
NullCount {
axes: Vec<String>,
fill: Option<f64>,
},
Covariance {
axes: Vec<String>,
},
Correlation {
axes: Vec<String>,
},
NanMean {
axes: Vec<String>,
},
NanStd {
axes: Vec<String>,
},
Transform {
method: TransformMethod,
axes: Vec<String>,
},
}
use super::transform_method::TransformMethod;
macro_rules! operation_axes_match {
($op:expr) => {
match $op {
Operation::Sum { axes }
| Operation::Mean { axes }
| Operation::Min { axes }
| Operation::Max { axes }
| Operation::Count { axes }
| Operation::Var { axes }
| Operation::Std { axes }
| Operation::Product { axes }
| Operation::NormL1 { axes }
| Operation::NormL2 { axes }
| Operation::AllFinite { axes }
| Operation::AnyNan { axes }
| Operation::AnyInf { axes }
| Operation::ArgMin { axes }
| Operation::ArgMax { axes }
| Operation::Median { axes }
| Operation::Quantile { axes, .. }
| Operation::Histogram { axes, .. }
| Operation::NanCount { axes }
| Operation::InfCount { axes }
| Operation::NullCount { axes, .. }
| Operation::Covariance { axes }
| Operation::Correlation { axes }
| Operation::NanMean { axes }
| Operation::NanStd { axes }
| Operation::Transform { axes, .. } => axes,
}
};
}
impl Operation {
#[must_use]
pub fn axes(&self) -> &[String] {
operation_axes_match!(self)
}
pub(crate) fn axes_mut(&mut self) -> &mut Vec<String> {
operation_axes_match!(self)
}
#[must_use]
pub fn wire_key(&self) -> &'static str {
match self {
Self::Sum { .. } => "sum",
Self::Mean { .. } => "mean",
Self::Min { .. } => "min",
Self::Max { .. } => "max",
Self::Count { .. } => "count",
Self::Var { .. } => "var",
Self::Std { .. } => "std",
Self::Product { .. } => "product",
Self::NormL1 { .. } => "norm_l1",
Self::NormL2 { .. } => "norm_l2",
Self::AllFinite { .. } => "all_finite",
Self::AnyNan { .. } => "any_nan",
Self::AnyInf { .. } => "any_inf",
Self::NanCount { .. } => "nan_count",
Self::InfCount { .. } => "inf_count",
Self::NullCount { .. } => "null_count",
Self::ArgMin { .. } => "arg_min",
Self::ArgMax { .. } => "arg_max",
Self::Median { .. } => "median",
Self::Quantile { .. } => "quantile",
Self::Histogram { .. } => "histogram",
Self::Covariance { .. } => "covariance",
Self::Correlation { .. } => "correlation",
Self::NanMean { .. } => "nan_mean",
Self::NanStd { .. } => "nan_std",
Self::Transform { .. } => "transform",
}
}
#[must_use]
pub fn requires_materialize(&self) -> bool {
matches!(
self,
Self::Median { .. }
| Self::Quantile { .. }
| Self::Histogram { .. }
| Self::Covariance { .. }
| Self::Correlation { .. }
)
}
#[must_use]
pub fn requires_transform(&self) -> bool {
matches!(self, Self::Transform { .. })
}
#[must_use]
pub fn transform_method(&self) -> Option<TransformMethod> {
match self {
Self::Transform { method, .. } => Some(*method),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WriteTarget {
#[default]
Switch,
Spill,
Sidecar,
Ram,
}
impl WriteTarget {
#[must_use]
pub const fn as_wire_str(self) -> &'static str {
match self {
Self::Switch => "switch",
Self::Spill => "spill",
Self::Sidecar => "sidecar",
Self::Ram => "ram",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct WriteHints {
pub target: WriteTarget,
pub path: Option<String>,
pub timestamp: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum OutputHint {
InlineJson,
SpillArray { handle: String },
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct OutputHints {
#[serde(default)]
pub preferred: Option<OutputHint>,
}
#[derive(Debug, Clone)]
pub struct QueryDocument {
pub layout_version: Option<u32>,
pub dataset: String,
pub selection: Option<Vec<AxisSlice>>,
pub operation: Option<Operation>,
pub output: Option<OutputHints>,
pub write: Option<WriteHints>,
pub execution: Option<ExecutionHints>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ExecutionHints {
#[serde(default)]
pub memory_budget_bytes: Option<u64>,
#[serde(default)]
pub memory_budget_percent_bps: Option<u16>,
#[serde(default)]
pub fold_parallel: Option<bool>,
#[serde(default, skip)]
pub device: Option<ExecutionDeviceHint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionDeviceHint {
Cpu,
Auto,
Metal,
Cuda(usize),
CudaMulti,
Rocm(usize),
RocmMulti,
}
impl ExecutionDeviceHint {
pub fn parse(token: &str) -> Result<Self, TetError> {
let t = token.trim();
if t.is_empty() {
return Err(TetError::Validation(
"device token must not be empty".into(),
));
}
if t.eq_ignore_ascii_case("cpu") {
return Ok(Self::Cpu);
}
if t.eq_ignore_ascii_case("auto") {
return Ok(Self::Auto);
}
if t.eq_ignore_ascii_case("metal") {
return Ok(Self::Metal);
}
if t.eq_ignore_ascii_case("cuda") {
return Ok(Self::Cuda(0));
}
if t.eq_ignore_ascii_case("cuda:multi") {
return Ok(Self::CudaMulti);
}
if let Some(rest) = t.strip_prefix("cuda:") {
let idx = rest.parse::<usize>().map_err(|_| {
TetError::Validation(format!(
"invalid device `{token}` (expected cuda:N with non-negative N)"
))
})?;
return Ok(Self::Cuda(idx));
}
if t.eq_ignore_ascii_case("rocm") {
return Ok(Self::Rocm(0));
}
if t.eq_ignore_ascii_case("rocm:multi") {
return Ok(Self::RocmMulti);
}
if let Some(rest) = t.strip_prefix("rocm:") {
let idx = rest.parse::<usize>().map_err(|_| {
TetError::Validation(format!(
"invalid device `{token}` (expected rocm:N with non-negative N)"
))
})?;
return Ok(Self::Rocm(idx));
}
Err(TetError::Validation(format!(
"unknown device `{token}` (expected cpu, auto, metal, cuda[:N| :multi], or rocm[:N| :multi])"
)))
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Cpu => "cpu",
Self::Auto => "auto",
Self::Metal => "metal",
Self::Cuda(0) => "cuda:0",
Self::CudaMulti => "cuda:multi",
Self::Rocm(0) => "rocm:0",
Self::RocmMulti => "rocm:multi",
Self::Cuda(n) => {
let _ = n;
"cuda"
}
Self::Rocm(n) => {
let _ = n;
"rocm"
}
}
}
#[must_use]
pub fn to_token(self) -> String {
match self {
Self::Cpu => "cpu".to_string(),
Self::Auto => "auto".to_string(),
Self::Metal => "metal".to_string(),
Self::Cuda(0) => "cuda".to_string(),
Self::Cuda(n) => format!("cuda:{n}"),
Self::CudaMulti => "cuda:multi".to_string(),
Self::Rocm(0) => "rocm".to_string(),
Self::Rocm(n) => format!("rocm:{n}"),
Self::RocmMulti => "rocm:multi".to_string(),
}
}
}