use qql_plan::QuantizationConfig;
use serde_json::Value;
use super::escape::escape_string;
use super::indexes::format_index_option;
pub fn format_quantization_config(quant: &QuantizationConfig) -> String {
match quant {
QuantizationConfig::Scalar { scalar } => {
let mut opts = vec!["type = 'scalar'".to_string()];
push_always_ram(&mut opts, scalar.always_ram);
push_memory(&mut opts, scalar.memory);
if let Some(quantile) = scalar.quantile
&& let Some(number) = serde_json::Number::from_f64(quantile)
{
opts.push(format!("quantile = {number}"));
}
opts.join(", ")
}
QuantizationConfig::Product { product } => {
let mut opts = vec!["type = 'product'".to_string()];
push_always_ram(&mut opts, product.always_ram);
opts.push(format!(
"compression = '{}'",
escape_string(&product.compression.to_ascii_lowercase())
));
push_memory(&mut opts, product.memory);
opts.join(", ")
}
QuantizationConfig::Binary { binary } => {
let mut opts = vec!["type = 'binary'".to_string()];
push_always_ram(&mut opts, binary.always_ram);
if let Some(encoding) = binary
.encoding
.as_deref()
.and_then(normalize_binary_encoding_str)
{
opts.push(format!("encoding = '{encoding}'"));
}
push_memory(&mut opts, binary.memory);
if let Some(query_encoding) = binary.query_encoding.as_deref() {
opts.push(format!(
"query_encoding = '{}'",
escape_string(query_encoding)
));
}
opts.join(", ")
}
QuantizationConfig::Turbo { turbo } => {
let mut opts = vec!["type = 'turbo'".to_string()];
push_always_ram(&mut opts, turbo.always_ram);
if let Some(bits) = turbo.bits.as_deref().and_then(normalize_turbo_bits_str) {
opts.push(format!("bits = {bits}"));
}
push_memory(&mut opts, turbo.memory);
opts.join(", ")
}
}
}
fn push_always_ram(opts: &mut Vec<String>, always_ram: Option<bool>) {
if let Some(value) = always_ram {
opts.push(format!("always_ram = {value}"));
}
}
fn push_memory(opts: &mut Vec<String>, memory: Option<qql_plan::types::MemoryPlacement>) {
if let Some(value) = memory {
opts.push(format!("memory = '{}'", value.as_str()));
}
}
fn normalize_binary_encoding_str(raw: &str) -> Option<String> {
Some(
match raw.to_ascii_lowercase().as_str() {
"one_bit" | "onebit" | "1" => "one_bit",
"two_bits" | "twobits" | "2" => "two_bits",
"one_and_half_bits" | "oneandhalfbits" | "1.5" => "one_and_half_bits",
_ => return None,
}
.to_string(),
)
}
fn normalize_turbo_bits_str(raw: &str) -> Option<String> {
Some(
match raw.to_ascii_lowercase().as_str() {
"bits1" | "1" => "1",
"bits1_5" | "bits1.5" | "1.5" => "1.5",
"bits2" | "2" => "2",
"bits4" | "4" => "4",
_ => return None,
}
.to_string(),
)
}
pub fn format_quantization_spec(quant: &Value) -> Option<String> {
let obj = quant.as_object()?;
for kind in ["scalar", "product", "binary", "turbo"] {
if let Some(inner) = obj.get(kind).and_then(|s| s.as_object()) {
return Some(format_quantization_opts(kind, inner));
}
}
if let Some(ty) = obj.get("type").and_then(|t| t.as_str()) {
return Some(format_quantization_opts(ty, obj));
}
if obj.get("disabled").and_then(|d| d.as_bool()) == Some(true) {
return Some("disabled = true".to_string());
}
None
}
pub fn format_quantization_opts(kind: &str, inner: &serde_json::Map<String, Value>) -> String {
let kind = kind.to_ascii_lowercase();
let mut opts = vec![format!("type = '{}'", kind)];
for (k, v) in inner {
if k == "type" {
continue;
}
if kind == "turbo" && (k == "bits" || k == "turbo_bits") {
if let Some(bits) = normalize_turbo_bits(v) {
opts.push(format!("bits = {}", bits));
}
continue;
}
if kind == "binary" && k == "encoding" {
if let Some(enc) = normalize_binary_encoding(v) {
opts.push(format!("encoding = '{}'", enc));
}
continue;
}
if let Some(opt) = format_index_option(k, v) {
opts.push(opt);
}
}
opts.join(", ")
}
pub fn normalize_binary_encoding(v: &Value) -> Option<String> {
let raw = match v {
Value::String(s) => s.to_ascii_lowercase(),
Value::Number(n) => {
let f = n.as_f64()?;
if (f - 1.5).abs() < f64::EPSILON {
"1.5".into()
} else if (f - 2.0).abs() < f64::EPSILON {
"2".into()
} else if (f - 1.0).abs() < f64::EPSILON {
"1".into()
} else {
return None;
}
}
_ => return None,
};
Some(
match raw.as_str() {
"one_bit" | "onebit" | "1" => "one_bit",
"two_bits" | "twobits" | "2" => "two_bits",
"one_and_half_bits" | "oneandhalfbits" | "1.5" => "one_and_half_bits",
_ => return None,
}
.into(),
)
}
pub fn normalize_turbo_bits(v: &Value) -> Option<String> {
match v {
Value::Number(n) => {
let f = n.as_f64()?;
if (f - 1.0).abs() < f64::EPSILON
|| (f - 1.5).abs() < f64::EPSILON
|| (f - 2.0).abs() < f64::EPSILON
|| (f - 4.0).abs() < f64::EPSILON
{
if f.fract() == 0.0 {
Some(format!("{}", f as i64))
} else {
Some(format!("{}", f))
}
} else {
None
}
}
Value::String(s) => match s.to_ascii_lowercase().as_str() {
"bits1" | "1" => Some("1".into()),
"bits1_5" | "bits1.5" | "1.5" => Some("1.5".into()),
"bits2" | "2" => Some("2".into()),
"bits4" | "4" => Some("4".into()),
_ => None,
},
_ => None,
}
}