#![allow(clippy::multiple_inherent_impl)]
use crate::{
Ctx, Group,
ast::{
self, BuilderScope, GroupComments, GroupFn, ParsingBuilder, fmt_comment_liberty,
},
common::f64_into_hash_ord_fn,
expression::logic,
table::{DisplayTableLookUp, DisplayValues, TableLookUp2D},
};
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Not as _, Sub};
use itertools::izip;
use strum::{Display, EnumString};
#[derive(
Debug, Clone, Copy, PartialEq, Display, EnumString, Default, Hash, Eq, PartialOrd, Ord
)]
#[derive(serde::Serialize, serde::Deserialize)]
pub enum TimingSenseType {
#[strum(serialize = "positive_unate")]
PositiveUnate,
#[strum(serialize = "negative_unate")]
NegativeUnate,
#[strum(serialize = "non_unate")]
#[default]
NonUnate,
}
impl TimingSenseType {
#[must_use]
#[inline]
pub fn compute_edge(&self, pin_edge: &logic::Edge) -> Option<logic::Edge> {
match self {
Self::PositiveUnate => Some(*pin_edge),
Self::NegativeUnate => Some(pin_edge.not()),
Self::NonUnate => None,
}
}
}
crate::ast::impl_self_builder!(TimingSenseType);
crate::ast::impl_simple!(TimingSenseType);
#[derive(Debug, Clone)]
#[derive(liberty_macros::Group)]
#[mut_set::derive::item]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(bound = "C::Other: serde::Serialize + serde::de::DeserializeOwned")]
pub struct CellDegradation<C: Ctx> {
#[liberty(name)]
#[id(borrow = str)]
pub name: String,
#[liberty(comments)]
comments: GroupComments,
#[liberty(extra_ctx)]
pub extra_ctx: C::Other,
#[liberty(attributes)]
pub attributes: ast::Attributes,
#[liberty(complex)]
pub index_1: Vec<f64>,
#[liberty(complex)]
pub values: Vec<f64>,
}
impl<C: Ctx> GroupFn<C> for CellDegradation<C> {}
#[derive(Debug, Clone, Default)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct TimingTableLookUp<C: Ctx> {
pub extra_ctx: C::Table,
pub name: String,
pub comments: String,
pub index_1: Vec<f64>,
pub index_2: Vec<f64>,
pub values: Vec<f64>,
pub lvf_index_1: Vec<f64>,
pub lvf_index_2: Vec<f64>,
pub lvf_values: Vec<LVFValue>,
}
#[expect(
clippy::similar_names,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
impl<C: Ctx> TimingTableLookUp<C> {
#[inline]
#[expect(clippy::needless_pass_by_ref_mut)]
pub(crate) fn use_common_template(
table: &mut Option<Self>,
scope: &mut BuilderScope<C>,
) {
if let Some(t) = table {
#[cfg(feature = "lut_template")]
crate::table::TableCtx::set_lut_template(
&mut t.extra_ctx,
scope.lu_table_template.get(&t.name),
);
}
}
#[inline]
const fn find_pos(len: usize, pos: usize) -> Option<(usize, usize)> {
if len <= 1 {
None
} else {
Some(if pos == 0 {
(0, 1)
} else if pos == len {
(len - 2, len - 1)
} else {
(pos - 1, pos)
})
}
}
#[inline]
fn get_value(&self, ix: usize, iy: usize) -> f64 {
self.values[ix * self.index_2.len() + iy]
}
#[inline]
fn get_lvf_value(&self, ix: usize, iy: usize) -> LVFValue {
self.lvf_values[ix * self.index_2.len() + iy]
}
#[must_use]
#[inline]
#[expect(clippy::float_arithmetic)]
pub fn lookup(&self, idx1: &f64, idx2: &f64) -> Option<f64> {
let idx1_ = f64_into_hash_ord_fn(idx1);
let idx2_ = f64_into_hash_ord_fn(idx2);
match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
Ok(i1_) => {
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => Some(self.get_value(i1_, i_1)),
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q_1 = self.get_value(i1_, i_1);
let q_2 = self.get_value(i1_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
(q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
}),
}
}
Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
let x1_ = self.index_1[i1_];
let x2_ = self.index_1[i2_];
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => {
let q1_ = self.get_value(i1_, i_1);
let q2_ = self.get_value(i2_, i_1);
Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
}
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q11 = self.get_value(i1_, i_1);
let q12 = self.get_value(i1_, i_2);
let q21 = self.get_value(i2_, i_1);
let q22 = self.get_value(i2_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
(q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
}),
}
}),
}
}
#[must_use]
#[inline]
#[expect(clippy::float_arithmetic)]
pub fn lookup_lvf(&self, idx1: &f64, idx2: &f64) -> Option<LVFValue> {
let idx1_ = f64_into_hash_ord_fn(idx1);
let idx2_ = f64_into_hash_ord_fn(idx2);
match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
Ok(i1_) => {
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => Some(self.get_lvf_value(i1_, i_1)),
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q_1 = self.get_lvf_value(i1_, i_1);
let q_2 = self.get_lvf_value(i1_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
(q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
}),
}
}
Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
let x1_ = self.index_1[i1_];
let x2_ = self.index_1[i2_];
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => {
let q1_ = self.get_lvf_value(i1_, i_1);
let q2_ = self.get_lvf_value(i2_, i_1);
Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
}
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q11 = self.get_lvf_value(i1_, i_1);
let q12 = self.get_lvf_value(i1_, i_2);
let q21 = self.get_lvf_value(i2_, i_1);
let q22 = self.get_lvf_value(i2_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
(q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
}),
}
}),
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, Copy)]
pub struct LVFValue {
pub mean: f64,
pub std_dev: f64,
pub skewness: f64,
}
impl LVFValue {
pub const MAX: Self = Self {
mean: f64::MAX,
std_dev: f64::MAX,
skewness: f64::MAX,
};
#[inline]
#[must_use]
#[expect(
clippy::as_conversions,
clippy::float_arithmetic,
clippy::cast_precision_loss,
clippy::arithmetic_side_effects
)]
pub fn estimate<I: Iterator<Item = f64>>(data: I) -> Option<Self> {
let mut n: usize = 0;
let mut mean = 0.0;
let mut m2 = 0.0; let mut m3 = 0.0;
for x in data {
n += 1;
let n_f = n as f64;
let delta = x - mean;
let delta_n = delta / n_f;
let term1 = delta * delta_n * (n_f - 1.0);
m3 += (term1 * delta_n).mul_add(n_f - 2.0, -(3.0 * delta_n * m2));
m2 += term1;
mean += delta_n;
}
if n == 0 {
return None;
}
let n_f = n as f64;
let std_dev = (m2 / n_f).sqrt();
let skewness = if n >= 2 && std_dev > 0.0 {
m3 / ((n_f - 1.0) * std_dev * std_dev * std_dev)
} else {
0.0
};
Some(Self { mean, std_dev, skewness })
}
#[inline]
#[must_use]
pub fn mul_add(self, a: f64, b: Self) -> Self {
Self {
mean: self.mean.mul_add(a, b.mean),
std_dev: self.std_dev.mul_add(a, b.std_dev),
skewness: self.skewness.mul_add(a, b.skewness),
}
}
}
impl PartialEq for LVFValue {
#[inline]
fn eq(&self, other: &Self) -> bool {
f64_into_hash_ord_fn(&self.mean) == f64_into_hash_ord_fn(&other.mean)
&& f64_into_hash_ord_fn(&self.std_dev) == f64_into_hash_ord_fn(&other.std_dev)
&& f64_into_hash_ord_fn(&self.skewness) == f64_into_hash_ord_fn(&other.skewness)
}
}
#[expect(clippy::float_arithmetic)]
impl Add for LVFValue {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Self {
mean: self.mean + rhs.mean,
std_dev: self.std_dev + rhs.std_dev,
skewness: self.skewness + rhs.skewness,
}
}
}
#[expect(clippy::float_arithmetic)]
impl AddAssign for LVFValue {
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.mean += rhs.mean;
self.std_dev += rhs.std_dev;
self.skewness += rhs.skewness;
}
}
#[expect(clippy::float_arithmetic)]
impl Sub for LVFValue {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
Self {
mean: self.mean - rhs.mean,
std_dev: self.std_dev - rhs.std_dev,
skewness: self.skewness - rhs.skewness,
}
}
}
#[expect(clippy::float_arithmetic)]
impl Mul<f64> for LVFValue {
type Output = Self;
#[inline]
fn mul(self, rhs: f64) -> Self::Output {
Self {
mean: self.mean * rhs,
std_dev: self.std_dev * rhs,
skewness: self.skewness * rhs,
}
}
}
#[expect(clippy::float_arithmetic)]
impl MulAssign<f64> for LVFValue {
#[inline]
fn mul_assign(&mut self, rhs: f64) {
self.mean *= rhs;
self.std_dev *= rhs;
self.skewness *= rhs;
}
}
#[expect(clippy::float_arithmetic)]
impl Div<f64> for LVFValue {
type Output = Self;
#[inline]
fn div(self, rhs: f64) -> Self::Output {
Self {
mean: self.mean / rhs,
std_dev: self.std_dev / rhs,
skewness: self.skewness / rhs,
}
}
}
#[expect(clippy::float_arithmetic)]
impl DivAssign<f64> for LVFValue {
#[inline]
fn div_assign(&mut self, rhs: f64) {
self.mean /= rhs;
self.std_dev /= rhs;
self.skewness /= rhs;
}
}
impl<C: Ctx> ParsingBuilder<C> for Option<TimingTableLookUp<C>> {
type Builder = (
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
);
#[inline]
#[expect(clippy::float_arithmetic)]
fn build(builder: Self::Builder, _scope: &mut BuilderScope<C>) -> Self {
#[inline]
fn eq_index<C: Ctx>(
lhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
rhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
) -> bool {
lhs.index_1 == rhs.index_1 && lhs.index_2 == rhs.index_2
}
let out: TimingTableLookUp<C> = match builder {
(Some(_value), Some(_mean_shift), Some(_std_dev), Some(_skewness)) => {
let lvf_nomial_same_index = eq_index(&_value, &_mean_shift);
let valid_lvf_index =
eq_index(&_mean_shift, &_std_dev) && eq_index(&_std_dev, &_skewness);
let (lvf_values, comments) = if valid_lvf_index {
(
izip!(
_value.values.inner.iter(),
_mean_shift.values.inner,
_std_dev.values.inner,
_skewness.values.inner
)
.map(|(value, mean_shift, std_dev, skewness)| {
let mean = value + mean_shift;
LVFValue { mean, std_dev, skewness }
})
.collect(),
String::new(),
)
} else {
crate::error!("LVF LUTs' index mismatch");
(Vec::new(), String::from("LVF LUTs' index mismatch"))
};
TimingTableLookUp {
extra_ctx: C::Table::default(),
name: _value.name,
comments,
index_1: _value.index_1,
index_2: _value.index_2,
values: _value.values.inner,
lvf_index_1: if lvf_nomial_same_index {
Vec::new()
} else {
_mean_shift.index_1
},
lvf_index_2: if lvf_nomial_same_index {
Vec::new()
} else {
_mean_shift.index_2
},
lvf_values,
}
}
(Some(_value), None, None, None) => TimingTableLookUp {
extra_ctx: C::Table::default(),
name: _value.name,
comments: String::new(),
index_1: _value.index_1,
index_2: _value.index_2,
values: _value.values.inner,
lvf_index_1: Vec::new(),
lvf_index_2: Vec::new(),
lvf_values: Vec::new(),
},
_ => return None,
};
Some(out)
}
}
impl<C: Ctx> ParsingBuilder<C> for TimingTableLookUp<C> {
type Builder = ();
fn build(_: Self::Builder, _: &mut BuilderScope<C>) -> Self {
unreachable!()
}
}
impl<C: Ctx> ast::GroupAttri<C> for TimingTableLookUp<C> {
#[inline]
#[expect(clippy::float_arithmetic)]
fn fmt_liberty<T: core::fmt::Write, I: ast::Indentation>(
&self,
key: &str,
f: &mut ast::CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
let chunk_size =
if self.index_2.is_empty() { self.values.len() } else { self.index_2.len() };
let len = self.values.len();
fmt_comment_liberty(Some(&self.comments), f)?;
DisplayTableLookUp {
name: &self.name,
index_1: &self.index_1,
index_2: &self.index_2,
values: DisplayValues {
len,
chunk_size,
inner: self.values.iter().copied(),
},
}
.fmt_self::<_, _, C>("", key, f)?;
if !self.lvf_values.is_empty() {
let mismatch_index = !self.lvf_index_1.is_empty();
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
len,
chunk_size,
inner: izip!(self.values.iter(), self.lvf_values.iter())
.map(|(value, lvf)| lvf.mean - value),
},
}
.fmt_self::<_, _, C>("ocv_mean_shift_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
len,
chunk_size,
inner: self.lvf_values.iter().map(|lvf| lvf.std_dev),
},
}
.fmt_self::<_, _, C>("ocv_std_dev_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
len,
chunk_size,
inner: self.lvf_values.iter().map(|lvf| lvf.skewness),
},
}
.fmt_self::<_, _, C>("ocv_skewness_", key, f)?;
}
Ok(())
}
fn nom_parse<'a, const IS_INCLUDED: bool>(
_: &mut Self::Builder,
_: &'a str,
_: &str,
_: &mut ast::ParseScope<'_>,
) -> nom::IResult<&'a str, Result<(), ast::IdError>, nom::error::Error<&'a str>> {
unreachable!()
}
}
impl<C: Ctx> Group<C> for TimingTableLookUp<C> {}