use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::any::Any;
use thiserror::Error;
use crate::DataType;
use crate::compressor::core::data::type_mask;
use crate::compressor::core::graphs::clustering as cluster;
use crate::compressor::core::graphs::ml_selector as ml;
#[derive(Error, Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ConfigError {
#[error("malformed configuration: {0}")]
Malformed(String),
#[error("invalid configuration: {0}")]
Invalid(String),
#[error("invalid model: {0}")]
Model(String),
}
impl From<ml::ConfigError> for ConfigError {
fn from(error: ml::ConfigError) -> Self {
match error {
ml::ConfigError::Cbor(_) => Self::Malformed(error.to_string()),
ml::ConfigError::Invalid(_) => Self::Invalid(error.to_string()),
}
}
}
impl From<cluster::ConfigError> for ConfigError {
fn from(error: cluster::ConfigError) -> Self {
match error {
cluster::ConfigError::Cbor(_) => Self::Malformed(error.to_string()),
cluster::ConfigError::Invalid(_) => Self::Invalid(error.to_string()),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct GbtNode {
pub feature: i32,
pub value: f32,
pub left: usize,
pub right: usize,
pub missing: usize,
}
impl GbtNode {
#[must_use]
pub const fn leaf(value: f32) -> Self {
Self {
feature: -1,
value,
left: 0,
right: 0,
missing: 0,
}
}
#[must_use]
pub const fn split(
feature: i32,
threshold: f32,
left: usize,
right: usize,
missing: usize,
) -> Self {
Self {
feature,
value: threshold,
left,
right,
missing,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GbtModel {
pub forests: Vec<Vec<Vec<GbtNode>>>,
pub successors: usize,
pub features: Vec<String>,
}
impl GbtModel {
fn to_core(&self) -> ml::Model {
ml::Model {
forests: self
.forests
.iter()
.map(|forest| {
forest
.iter()
.map(|tree| {
tree.iter()
.map(|node| ml::Node {
feature_idx: node.feature,
value: node.value,
left: node.left,
right: node.right,
missing: node.missing,
})
.collect()
})
.collect()
})
.collect(),
generator: ml::FeatureGen::Integer,
nb_successors: self.successors,
feature_labels: self
.features
.iter()
.map(|name| name.as_bytes().to_vec())
.collect(),
}
}
}
#[derive(Clone)]
pub struct MlSelectorConfig {
blob: Arc<[u8]>,
parsed: Arc<ml::Config>,
}
impl core::fmt::Debug for MlSelectorConfig {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MlSelectorConfig")
.field("bytes", &self.blob.len())
.finish_non_exhaustive()
}
}
impl PartialEq for MlSelectorConfig {
fn eq(&self, other: &Self) -> bool {
self.blob == other.blob
}
}
impl TryFrom<&[u8]> for MlSelectorConfig {
type Error = ConfigError;
fn try_from(blob: &[u8]) -> Result<Self, ConfigError> {
let parsed = ml::deserialize(blob)?;
Ok(Self {
blob: Arc::from(blob),
parsed: Arc::new(parsed),
})
}
}
impl TryFrom<&GbtModel> for MlSelectorConfig {
type Error = ConfigError;
fn try_from(model: &GbtModel) -> Result<Self, ConfigError> {
let core = model.to_core();
core.validate()
.map_err(|message| ConfigError::Model(message.to_string()))?;
let blob = ml::serialize(&core)?;
Self::try_from(&blob[..])
}
}
impl AsRef<[u8]> for MlSelectorConfig {
fn as_ref(&self) -> &[u8] {
&self.blob
}
}
impl MlSelectorConfig {
pub fn untrained(successors: usize) -> Result<Self, ConfigError> {
let forests = if successors == 2 { 1 } else { successors };
Self::try_from(&GbtModel {
forests: (0..forests)
.map(|_| vec![vec![GbtNode::leaf(-1.0)]])
.collect(),
successors,
features: vec!["placeholder".to_string()],
})
}
pub(crate) fn materialized(&self) -> Arc<dyn Any + Send + Sync> {
Arc::clone(&self.parsed) as Arc<dyn Any + Send + Sync>
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Target {
ty: DataType,
width: usize,
successor: usize,
codec: usize,
}
impl Target {
fn to_core(self) -> cluster::TypeSuccessor {
cluster::TypeSuccessor {
ty: u32::from(type_mask(self.ty)),
elt_width: self.width,
successor: self.successor,
codec: self.codec,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ClusteringConfig {
clusters: Vec<(Target, Vec<i32>)>,
defaults: Vec<Target>,
}
impl ClusteringConfig {
#[must_use]
pub fn with_cluster(
mut self,
ty: DataType,
width: usize,
successor: usize,
codec: usize,
tags: impl Into<Vec<i32>>,
) -> Self {
let target = Target {
ty,
width,
successor,
codec,
};
self.clusters.push((target, tags.into()));
self
}
#[must_use]
pub fn with_type_default(
mut self,
ty: DataType,
width: usize,
successor: usize,
codec: usize,
) -> Self {
self.defaults.push(Target {
ty,
width,
successor,
codec,
});
self
}
fn to_core(&self) -> cluster::Config {
cluster::Config {
clusters: self
.clusters
.iter()
.map(|(target, tags)| cluster::Cluster {
type_successor: target.to_core(),
member_tags: tags.clone(),
})
.collect(),
type_defaults: self
.defaults
.iter()
.map(|target| target.to_core())
.collect(),
}
}
pub(crate) fn needs(&self) -> (usize, usize) {
self.clusters
.iter()
.map(|(target, _)| target)
.chain(&self.defaults)
.fold((0, 0), |(s, c), target| {
(s.max(target.successor + 1), c.max(target.codec + 1))
})
}
}
impl From<&ClusteringConfig> for Vec<u8> {
fn from(config: &ClusteringConfig) -> Self {
cluster::serialize(&config.to_core()).unwrap_or_default()
}
}
impl TryFrom<&[u8]> for ClusteringConfig {
type Error = ConfigError;
fn try_from(blob: &[u8]) -> Result<Self, ConfigError> {
let core = cluster::deserialize(blob)?;
let target = |ts: &cluster::TypeSuccessor| {
let ty = match ts.ty {
1 => DataType::Serial,
2 => DataType::Struct,
4 => DataType::Numeric,
8 => DataType::String,
_ => return Err(ConfigError::Invalid("unknown stream type".to_string())),
};
Ok(Target {
ty,
width: ts.elt_width,
successor: ts.successor,
codec: ts.codec,
})
};
Ok(Self {
clusters: core
.clusters
.iter()
.map(|c| Ok((target(&c.type_successor)?, c.member_tags.clone())))
.collect::<Result<_, ConfigError>>()?,
defaults: core
.type_defaults
.iter()
.map(target)
.collect::<Result<_, _>>()?,
})
}
}