liberty_db/timing/
items.rs

1//! All item structure inside
2//! `Timing`.
3#![allow(clippy::multiple_inherent_impl)]
4use crate::{
5  Ctx, Group,
6  ast::{
7    self, BuilderScope, GroupComments, GroupFn, ParsingBuilder, fmt_comment_liberty,
8  },
9  common::f64_into_hash_ord_fn,
10  expression::logic,
11  table::{
12    DisplayTableLookUp, DisplayValues, OcvSigmaTable, OcvSigmaTableBuilder, SigmaType,
13    TableLookUp2D, TableLookUp2DBuilder,
14  },
15};
16use core::iter::zip;
17use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Not as _, Sub};
18use itertools::izip;
19use strum::{Display, EnumString};
20/// The `timing_sense` attribute describes the way an input pin logically affects an output pin.
21///
22/// <a name ="reference_link" href="
23/// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=328.32+329.2+330.2&end=328.33+329.39+330.6
24/// ">Reference</a>
25///
26/// #### Syntax
27/// `timing_sense : positive_unate | negative_unate | non_unate ;`
28///
29/// `positive_unate`: Combines incoming rise delays with local rise delays and
30/// compares incoming fall delays with local fall delays.
31///
32/// `negative_unate`: Combines incoming rise delays with local fall delays and
33/// compares incoming fall delays with local rise delays.
34///
35/// `non_unate`: Combines local delays with the worst-case incoming delay value.
36/// The non-unate timing sense represents a function whose output value change cannot
37/// be determined from the direction of the change in the input value.
38///
39/// Timing sense is derived from the logic function of a pin. For example, the value derived for
40/// an AND gate is `positive_unate`, the value for a NAND gate is `negative_unate`, and the value
41/// for an XOR gate is `non_unate`.
42///
43/// A function is said to be unate if a rising (falling) change on a positive (negative) unate
44/// input variable causes the output function variable to rise (fall) or not change.
45/// For a non-unate variable, further state information is required to determine the effects of
46/// a particular state transition.
47///
48/// You can specify half-unate sequential timing arcs if the `timing_type` value is either
49/// `rising_edge` or `falling_edge` and the `timing_sense` value is either `positive_unate`
50/// or `negative_unate`.
51/// + In the case of `rising_edge` and `positive_unate` values, only the `cell_rise` and `rise_transition` information is required.
52/// + In the case of `rising_edge` and `negative_unate` values, only the `cell_fall` and `fall_transition` information is required.
53/// + In the case of `falling_edge` and `positive_unate` values, only the `cell_rise` and `rise_transition` information is required.
54/// + In the case of `falling_edge` and `negative_unate` values, only the `cell_fall` and `fall_transition` information is required.
55///
56/// Do not define the `timing_sense` value of a pin, except when you need to override the derived value
57/// or when you are characterizing a noncombinational gate such as a three-state component. For example,
58/// you might want to define the timing sense manually when you model multiple paths between
59/// an input pin and an output pin, such as in an XOR gate.
60///
61/// It is possible that one path is positive unate while another is negative unate. In this case,
62/// the first timing arc is given a `positive_unate` designation and the second is given a `negative_unate`
63/// designation.
64///
65/// Timing arcs with a timing type of `clear` or `preset` require a `timing_sense` attribute.
66/// If `related_pin` is an output pin, you must define a `timing_sense` attribute for that pin.
67#[derive(
68  Debug, Clone, Copy, PartialEq, Display, EnumString, Default, Hash, Eq, PartialOrd, Ord
69)]
70#[derive(serde::Serialize, serde::Deserialize)]
71pub enum TimingSenseType {
72  /// Combines incoming `rise` delays with local `rise` delays
73  /// and compares incoming `fall` delays with local `fall` delays.
74  /// <a name ="reference_link" href="
75  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.5&end=329.6
76  /// ">Reference</a>
77  #[strum(serialize = "positive_unate")]
78  PositiveUnate,
79  /// Combines incoming `rise` delays with local `fall` delays
80  /// and compares incoming `fall` delays with local `rise` delays.
81  /// <a name ="reference_link" href="
82  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.8&end=329.9
83  /// ">Reference</a>
84  #[strum(serialize = "negative_unate")]
85  NegativeUnate,
86  /// Combines local delays with the `worst-case` incoming delay value.
87  /// The non-unate timing sense represents a function whose
88  /// output value change cannot be determined from the direction
89  /// of the change in the input value.
90  /// <a name ="reference_link" href="
91  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.11&end=329.13
92  /// ">Reference</a>
93  #[strum(serialize = "non_unate")]
94  #[default]
95  NonUnate,
96}
97
98impl TimingSenseType {
99  #[must_use]
100  #[inline]
101  pub fn compute_edge(&self, pin_edge: &logic::Edge) -> Option<logic::Edge> {
102    match self {
103      Self::PositiveUnate => Some(*pin_edge),
104      Self::NegativeUnate => Some(pin_edge.not()),
105      Self::NonUnate => None,
106    }
107  }
108}
109crate::ast::impl_self_builder!(TimingSenseType);
110crate::ast::impl_simple!(TimingSenseType);
111
112/// The `cell_degradation`  group describes a cell performance degradation
113/// design rule for compiling a design.
114///
115/// A cell degradation design rule specifies the maximum capacitive load
116/// a cell can drive without causing cell performance degradation during the fall transition.
117/// <a name ="reference_link" href="
118/// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=347.33&end=347.35
119/// ">Reference</a>
120///
121#[derive(Debug, Clone)]
122#[derive(liberty_macros::Group)]
123#[mut_set::derive::item]
124#[derive(serde::Serialize, serde::Deserialize)]
125#[serde(bound = "C::Other: serde::Serialize + serde::de::DeserializeOwned")]
126pub struct CellDegradation<C: 'static + Ctx> {
127  /// name
128  #[liberty(name)]
129  #[id(borrow = str)]
130  pub name: String,
131  /// group comments
132  #[liberty(comments)]
133  comments: GroupComments,
134  #[liberty(extra_ctx)]
135  pub extra_ctx: C::Other,
136  /// group undefined attributes
137  #[liberty(attributes)]
138  pub attributes: ast::Attributes,
139  /// /* lookup table */
140  /// <a name ="reference_link" href="
141  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=348.6&end=348.7
142  /// ">Reference</a>
143  #[liberty(complex)]
144  pub index_1: Vec<f64>,
145  /// /* lookup table */
146  /// <a name ="reference_link" href="
147  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=348.6&end=348.7
148  /// ">Reference</a>
149  #[liberty(complex)]
150  pub values: Vec<f64>,
151}
152impl<C: 'static + Ctx> GroupFn<C> for CellDegradation<C> {}
153
154#[derive(Debug, Clone, Default)]
155#[derive(serde::Serialize, serde::Deserialize)]
156pub struct TimingTableLookUp<C: 'static + Ctx> {
157  pub extra_ctx: C::Table,
158  pub name: String,
159  pub comments: String,
160  pub index_1: Vec<f64>,
161  pub index_2: Vec<f64>,
162  pub values: Vec<f64>,
163  pub lvf_moments_values: Vec<LVFMoments>,
164  pub lvf_early_late_values: Vec<LVFEarlyLate>,
165}
166#[expect(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
167impl<C: 'static + Ctx> TimingTableLookUp<C> {
168  #[inline]
169  pub(crate) fn use_common_template(
170    table: &mut Option<Self>,
171    scope: &mut BuilderScope<C>,
172  ) {
173    if let Some(t) = table {
174      #[cfg(feature = "lut_template")]
175      crate::table::TableCtx::set_lut_template(
176        &mut t.extra_ctx,
177        scope.lu_table_template.get(&t.name),
178      );
179    }
180  }
181  #[inline]
182  const fn find_pos(len: usize, pos: usize) -> Option<(usize, usize)> {
183    if len <= 1 {
184      None
185    } else {
186      Some(if pos == 0 {
187        (0, 1)
188      } else if pos == len {
189        (len - 2, len - 1)
190      } else {
191        (pos - 1, pos)
192      })
193    }
194  }
195  #[inline]
196  fn get_value(&self, ix: usize, iy: usize) -> Option<f64> {
197    self.values.get(ix * self.index_2.len() + iy).copied()
198  }
199  #[inline]
200  fn get_lvf_moments_value(&self, ix: usize, iy: usize) -> Option<LVFMoments> {
201    self.lvf_moments_values.get(ix * self.index_2.len() + iy).copied()
202  }
203  /// The linear interpolation & extrapolation
204  #[must_use]
205  #[inline]
206  #[expect(clippy::float_arithmetic)]
207  pub fn lookup(&self, idx1: &f64, idx2: &f64) -> Option<f64> {
208    let idx1_ = f64_into_hash_ord_fn(idx1);
209    let idx2_ = f64_into_hash_ord_fn(idx2);
210    match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
211      Ok(i1_) => {
212        match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
213          Ok(i_1) => self.get_value(i1_, i_1),
214          Err(pos2) => {
215            let (i_1, i_2) = Self::find_pos(self.index_2.len(), pos2)?;
216            let q_1 = self.get_value(i1_, i_1)?;
217            let q_2 = self.get_value(i1_, i_2)?;
218            let x_1 = self.index_2[i_1];
219            let x_2 = self.index_2[i_2];
220            // q_1 + (q_2 - q_1) * ((idx2 - x_1) / (x_2 - x_1))
221            Some((q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1))
222          }
223        }
224      }
225      Err(pos1) => {
226        let (i1_, i2_) = Self::find_pos(self.index_1.len(), pos1)?;
227        let x1_ = self.index_1[i1_];
228        let x2_ = self.index_1[i2_];
229        match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
230          Ok(i_1) => {
231            let q1_ = self.get_value(i1_, i_1)?;
232            let q2_ = self.get_value(i2_, i_1)?;
233            // Some(q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_)))
234            Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
235          }
236          Err(pos2) => {
237            let (i_1, i_2) = Self::find_pos(self.index_2.len(), pos2)?;
238            let q11 = self.get_value(i1_, i_1)?;
239            let q12 = self.get_value(i1_, i_2)?;
240            let q21 = self.get_value(i2_, i_1)?;
241            let q22 = self.get_value(i2_, i_2)?;
242            let x_1 = self.index_2[i_1];
243            let x_2 = self.index_2[i_2];
244            // let q1_ = q11 + (q12 - q11) * ((idx2 - x_1) / (x_2 - x_1));
245            let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
246            // let q2_ = q21 + (q22 - q21) * ((idx2 - x_1) / (x_2 - x_1));
247            let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
248            // q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_))
249            Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
250          }
251        }
252      }
253    }
254  }
255  #[must_use]
256  #[inline]
257  #[expect(clippy::float_arithmetic)]
258  pub fn lookup_lvf_moments(&self, idx1: &f64, idx2: &f64) -> Option<LVFMoments> {
259    let idx1_ = f64_into_hash_ord_fn(idx1);
260    let idx2_ = f64_into_hash_ord_fn(idx2);
261    match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
262      Ok(i1_) => {
263        match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
264          Ok(i_1) => self.get_lvf_moments_value(i1_, i_1),
265          Err(pos2) => {
266            let (i_1, i_2) = Self::find_pos(self.index_2.len(), pos2)?;
267            let q_1 = self.get_lvf_moments_value(i1_, i_1)?;
268            let q_2 = self.get_lvf_moments_value(i1_, i_2)?;
269            let x_1 = self.index_2[i_1];
270            let x_2 = self.index_2[i_2];
271            // q_1 + (q_2 - q_1) * ((idx2 - x_1) / (x_2 - x_1))
272            Some((q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1))
273          }
274        }
275      }
276      Err(pos1) => {
277        let (i1_, i2_) = Self::find_pos(self.index_1.len(), pos1)?;
278        let x1_ = self.index_1[i1_];
279        let x2_ = self.index_1[i2_];
280        match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
281          Ok(i_1) => {
282            let q1_ = self.get_lvf_moments_value(i1_, i_1)?;
283            let q2_ = self.get_lvf_moments_value(i2_, i_1)?;
284            // Some(q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_)))
285            Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
286          }
287          Err(pos2) => {
288            let (i_1, i_2) = Self::find_pos(self.index_2.len(), pos2)?;
289            let q11 = self.get_lvf_moments_value(i1_, i_1)?;
290            let q12 = self.get_lvf_moments_value(i1_, i_2)?;
291            let q21 = self.get_lvf_moments_value(i2_, i_1)?;
292            let q22 = self.get_lvf_moments_value(i2_, i_2)?;
293            let x_1 = self.index_2[i_1];
294            let x_2 = self.index_2[i_2];
295            // let q1_ = q11 + (q12 - q11) * ((idx2 - x_1) / (x_2 - x_1));
296            let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
297            // let q2_ = q21 + (q22 - q21) * ((idx2 - x_1) / (x_2 - x_1));
298            let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
299            // q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_))
300            Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
301          }
302        }
303      }
304    }
305  }
306}
307
308#[derive(serde::Serialize, serde::Deserialize)]
309#[derive(Debug, Default, Clone, Copy)]
310pub struct LVFMoments {
311  /// `mean` = `nominal` + `mean_shift`
312  pub mean: f64,
313  pub std_dev: f64,
314  pub skewness: f64,
315}
316#[derive(serde::Serialize, serde::Deserialize)]
317#[derive(Debug, Default, Clone, Copy)]
318pub struct LVFEarlyLate {
319  pub early_sigma: f64,
320  pub late_sigma: f64,
321}
322#[expect(clippy::float_arithmetic)]
323impl LVFMoments {
324  /// Cornish–Fisher:
325  /// `quantile ≈ μ + σ[ z + γ*​​(z^2−1)/6 − γ^2*​​(2*z^3−5*z)/36 ]`
326  const Z: f64 = 3.0;
327  const TMP1: f64 = (Self::Z * Self::Z - 1.0) / 6.0;
328  const TMP2: f64 = (2.0 * Self::Z * Self::Z * Self::Z - 5.0 * Self::Z) / 36.0;
329  #[inline]
330  #[must_use]
331  #[deprecated = "TODO"]
332  /// `q+ ​​= μ + σ[ z + γ*TMP1 − γ^2*TMP2 ] = z*late_sigma - nominal`
333  ///
334  /// `​q− = μ + σ[−z + γ*TMP1 + γ^2*TMP2 ] = nominal - z*early_sigma`
335  pub const fn to_early_late(&self, nominal: f64) -> Option<LVFEarlyLate> {
336    if !(self.std_dev >= 0.0
337      && nominal.is_finite()
338      && self.mean.is_finite()
339      && self.std_dev.is_finite()
340      && self.skewness.is_finite())
341    {
342      return None;
343    }
344    let quantile_plus_3sigma = self.mean
345      + self.std_dev
346        * (self.skewness * Self::TMP1 + Self::Z
347          - self.skewness * self.skewness * Self::TMP2);
348    let quantile_minus_3sigma = self.mean
349      + self.std_dev
350        * (self.skewness * Self::TMP1 - Self::Z
351          + self.skewness * self.skewness * Self::TMP2);
352    Some(LVFEarlyLate {
353      early_sigma: (nominal - quantile_minus_3sigma) / Self::Z,
354      late_sigma: (quantile_plus_3sigma - nominal) / Self::Z,
355    })
356  }
357}
358
359#[expect(clippy::float_arithmetic)]
360impl LVFEarlyLate {
361  /// `q+ ​​= μ + σ[ z + γ*TMP1 − γ^2*TMP2 ] = z*late_sigma - nominal`
362  ///
363  /// `​q− = μ + σ[−z + γ*TMP1 + γ^2*TMP2 ] = nominal - z*early_sigma`
364  ///
365  /// `Δ = q+ - q- = z*(early_sigma+late_sigma) - 2*nominal`
366  ///
367  /// `a = (q+ + q-)/2 = z*(late_sigma-early_sigma)/2`
368  #[inline]
369  #[must_use]
370  #[expect(clippy::suboptimal_flops)]
371  #[deprecated = "TODO"]
372  pub fn to_moments(&self, nominal: f64, mean: f64) -> Option<LVFMoments> {
373    if !(self.early_sigma >= 0.0
374      && self.late_sigma >= 0.0
375      && nominal.is_finite()
376      && mean.is_finite()
377      && self.early_sigma.is_finite()
378      && self.late_sigma.is_finite())
379    {
380      return None;
381    }
382
383    let delta = LVFMoments::Z * (self.early_sigma + self.late_sigma) - 2.0 * nominal;
384    let a = LVFMoments::Z * (self.late_sigma - self.early_sigma) / 2.0;
385    let a_minus_mean = a - mean;
386
387    // K = 2*TMP2*(a-mean)^2 / TMP1^2
388    let k = 2.0 * LVFMoments::TMP2 * a_minus_mean * a_minus_mean
389      / (LVFMoments::TMP1 * LVFMoments::TMP1);
390
391    // disc = delta^2 + 8*Z*k
392    let disc = delta * delta + 8.0 * LVFMoments::Z * k;
393    if !disc.is_finite() {
394      return None;
395    }
396    let std_dev = (delta + disc.sqrt()) / (4.0 * LVFMoments::Z);
397    if !(std_dev.is_finite() && std_dev >= 0.0) {
398      return None;
399    } // skewness=0 when std_dev==0
400    let skewness =
401      if std_dev > 0.0 { a_minus_mean / (std_dev * LVFMoments::TMP1) } else { 0.0 };
402
403    Some(LVFMoments { mean, std_dev, skewness })
404  }
405}
406
407impl LVFMoments {
408  /// PeBay/West one-pass estimations
409  /// <https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online>
410  #[inline]
411  #[expect(
412    clippy::as_conversions,
413    clippy::float_arithmetic,
414    clippy::cast_precision_loss,
415    clippy::arithmetic_side_effects
416  )]
417  pub fn estimate<E, I: Iterator<Item = Result<f64, E>>>(
418    data: I,
419  ) -> Result<Option<Self>, E> {
420    let mut n: usize = 0;
421    let mut mean = 0.0;
422    let mut m2 = 0.0; // sum (x - mean)^2
423    let mut m3 = 0.0; // sum (x - mean)^3
424
425    for x in data {
426      n += 1;
427      let n_f = n as f64;
428
429      // PeBay/West one-pass
430      let delta = x? - mean;
431      let delta_n = delta / n_f;
432      let term1 = delta * delta_n * (n_f - 1.0);
433
434      m3 += (term1 * delta_n).mul_add(n_f - 2.0, -(3.0 * delta_n * m2));
435      // m3 += term1 * delta_n * (n_f - 2.0) - 3.0 * delta_n * m2;
436      m2 += term1;
437      mean += delta_n;
438    }
439
440    if n == 0 {
441      return Ok(None);
442    }
443
444    let n_f = n as f64;
445    let std_dev = (m2 / n_f).sqrt();
446    let skewness = if n >= 2 && std_dev > 0.0 {
447      m3 / ((n_f - 1.0) * std_dev * std_dev * std_dev)
448    } else {
449      0.0
450    };
451
452    Ok(Some(Self { mean, std_dev, skewness }))
453  }
454  #[inline]
455  #[must_use]
456  /// self * a + b
457  pub fn mul_add(self, a: f64, b: Self) -> Self {
458    Self {
459      mean: self.mean.mul_add(a, b.mean),
460      std_dev: self.std_dev.mul_add(a, b.std_dev),
461      skewness: self.skewness.mul_add(a, b.skewness),
462    }
463  }
464}
465impl PartialEq for LVFMoments {
466  #[inline]
467  fn eq(&self, other: &Self) -> bool {
468    f64_into_hash_ord_fn(&self.mean) == f64_into_hash_ord_fn(&other.mean)
469      && f64_into_hash_ord_fn(&self.std_dev) == f64_into_hash_ord_fn(&other.std_dev)
470      && f64_into_hash_ord_fn(&self.skewness) == f64_into_hash_ord_fn(&other.skewness)
471  }
472}
473#[expect(clippy::float_arithmetic)]
474impl Add for LVFMoments {
475  type Output = Self;
476  #[inline]
477  fn add(self, rhs: Self) -> Self::Output {
478    Self {
479      mean: self.mean + rhs.mean,
480      std_dev: self.std_dev + rhs.std_dev,
481      skewness: self.skewness + rhs.skewness,
482    }
483  }
484}
485#[expect(clippy::float_arithmetic)]
486impl AddAssign for LVFMoments {
487  #[inline]
488  fn add_assign(&mut self, rhs: Self) {
489    self.mean += rhs.mean;
490    self.std_dev += rhs.std_dev;
491    self.skewness += rhs.skewness;
492  }
493}
494#[expect(clippy::float_arithmetic)]
495impl AddAssign for LVFEarlyLate {
496  #[inline]
497  fn add_assign(&mut self, rhs: Self) {
498    self.early_sigma += rhs.early_sigma;
499    self.late_sigma += rhs.late_sigma;
500  }
501}
502#[expect(clippy::float_arithmetic)]
503impl Sub for LVFMoments {
504  type Output = Self;
505  #[inline]
506  /// TODO: check
507  fn sub(self, rhs: Self) -> Self::Output {
508    Self {
509      mean: self.mean - rhs.mean,
510      // TODO: check - or + ?
511      std_dev: self.std_dev - rhs.std_dev,
512      skewness: self.skewness - rhs.skewness,
513    }
514  }
515}
516#[expect(clippy::float_arithmetic)]
517impl Mul<f64> for LVFMoments {
518  type Output = Self;
519  #[inline]
520  fn mul(self, rhs: f64) -> Self::Output {
521    Self {
522      mean: self.mean * rhs,
523      std_dev: self.std_dev * rhs,
524      skewness: self.skewness * rhs,
525    }
526  }
527}
528#[expect(clippy::float_arithmetic)]
529impl MulAssign<f64> for LVFMoments {
530  #[inline]
531  fn mul_assign(&mut self, rhs: f64) {
532    self.mean *= rhs;
533    self.std_dev *= rhs;
534    self.skewness *= rhs;
535  }
536}
537#[expect(clippy::float_arithmetic)]
538impl Div<f64> for LVFMoments {
539  type Output = Self;
540  #[inline]
541  fn div(self, rhs: f64) -> Self::Output {
542    Self {
543      mean: self.mean / rhs,
544      std_dev: self.std_dev / rhs,
545      skewness: self.skewness / rhs,
546    }
547  }
548}
549#[expect(clippy::float_arithmetic)]
550impl DivAssign<f64> for LVFMoments {
551  #[inline]
552  fn div_assign(&mut self, rhs: f64) {
553    self.mean /= rhs;
554    self.std_dev /= rhs;
555    self.skewness /= rhs;
556  }
557}
558#[expect(clippy::float_arithmetic)]
559impl DivAssign<f64> for LVFEarlyLate {
560  #[inline]
561  fn div_assign(&mut self, rhs: f64) {
562    self.early_sigma /= rhs;
563    self.late_sigma /= rhs;
564  }
565}
566
567impl<C: 'static + Ctx> ParsingBuilder<C> for Option<TimingTableLookUp<C>> {
568  /// `value`, `mean_shift`, `std_dev`, `skewness`, `early/late-sigma`
569  type Builder = (
570    // value
571    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
572    // mean_shift
573    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
574    // std_dev
575    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
576    // skewness
577    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
578    // early/late sigma
579    Vec<<OcvSigmaTable<C> as ParsingBuilder<C>>::Builder>,
580  );
581  #[inline]
582  #[expect(clippy::float_arithmetic, clippy::too_many_lines)]
583  fn build(builder: Self::Builder, _scope: &mut BuilderScope<C>) -> Self {
584    #[inline]
585    fn eq_index<C: 'static + Ctx>(
586      lhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
587      rhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
588    ) -> bool {
589      lhs.index_1 == rhs.index_1
590        && lhs.index_2 == rhs.index_2
591        && lhs.values.inner.len() == rhs.values.inner.len()
592    }
593    #[inline]
594    fn ocv_eq_index<C: 'static + Ctx>(
595      lhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
596      rhs: &<OcvSigmaTable<C> as ParsingBuilder<C>>::Builder,
597    ) -> bool {
598      lhs.index_1 == rhs.index_1
599        && lhs.index_2 == rhs.index_2
600        && lhs.values.inner.len() == rhs.values.inner.len()
601    }
602    fn obtain_ocv_sigma<C: 'static + Ctx>(
603      value: &TableLookUp2DBuilder<C>,
604      comments: &mut String,
605      ocv_sigma: Vec<OcvSigmaTableBuilder<C>>,
606    ) -> Vec<LVFEarlyLate> {
607      let mut early = None;
608      let mut late = None;
609      let mut early_late = None;
610      for table in ocv_sigma {
611        match table.sigma_type {
612          SigmaType::Early => early = Some(table),
613          SigmaType::Late => late = Some(table),
614          SigmaType::EarlyAndLate => early_late = Some(table),
615        }
616      }
617      if let (Some(early_table), Some(late_table)) = (early, late) {
618        if ocv_eq_index(value, &early_table) && ocv_eq_index(value, &late_table) {
619          zip(early_table.values.inner, late_table.values.inner)
620            .map(|(early_sigma, late_sigma)| LVFEarlyLate { early_sigma, late_sigma })
621            .collect()
622        } else {
623          crate::error!("LVF early_late LUTs' index mismatch");
624          comments.push_str("LVF early_late LUTs' index mismatch");
625          Vec::new()
626        }
627      } else if let Some(early_late_table) = early_late {
628        if ocv_eq_index(value, &early_late_table) {
629          early_late_table
630            .values
631            .inner
632            .into_iter()
633            .map(|sigma| LVFEarlyLate { early_sigma: sigma, late_sigma: sigma })
634            .collect()
635        } else {
636          crate::error!("LVF early_late LUTs' index mismatch");
637          comments.push_str("LVF early_late LUTs' index mismatch");
638          Vec::new()
639        }
640      } else {
641        Vec::new()
642      }
643    }
644    let mut comments = String::new();
645    match builder {
646      (Some(_value), Some(_mean_shift), Some(_std_dev), Some(_skewness), ocv_sigma) => {
647        let lvf_moments_values = if eq_index(&_value, &_mean_shift)
648          && eq_index(&_mean_shift, &_std_dev)
649          && eq_index(&_std_dev, &_skewness)
650        {
651          izip!(
652            _value.values.inner.iter(),
653            _mean_shift.values.inner,
654            _std_dev.values.inner,
655            _skewness.values.inner
656          )
657          .map(|(value, mean_shift, std_dev, skewness)| {
658            let mean = value + mean_shift;
659            LVFMoments { mean, std_dev, skewness }
660          })
661          .collect()
662        } else {
663          crate::error!("LVF moments LUTs' index mismatch");
664          comments.push_str("LVF moments LUTs' index mismatch");
665          Vec::new()
666        };
667        let lvf_early_late_values = obtain_ocv_sigma(&_value, &mut comments, ocv_sigma);
668        Some(TimingTableLookUp {
669          extra_ctx: C::Table::default(),
670          name: _value.name,
671          comments,
672          index_1: _value.index_1,
673          index_2: _value.index_2,
674          values: _value.values.inner,
675          lvf_moments_values,
676          lvf_early_late_values,
677        })
678      }
679      (Some(_value), None, None, None, ocv_sigma) => {
680        let lvf_early_late_values = obtain_ocv_sigma(&_value, &mut comments, ocv_sigma);
681        Some(TimingTableLookUp {
682          extra_ctx: C::Table::default(),
683          name: _value.name,
684          comments,
685          index_1: _value.index_1,
686          index_2: _value.index_2,
687          values: _value.values.inner,
688          lvf_moments_values: Vec::new(),
689          lvf_early_late_values,
690        })
691      }
692      _ => None,
693    }
694  }
695}
696impl<C: 'static + Ctx> ParsingBuilder<C> for TimingTableLookUp<C> {
697  type Builder = ();
698  fn build(_: Self::Builder, _: &mut BuilderScope<C>) -> Self {
699    unreachable!()
700  }
701}
702impl<C: 'static + Ctx> ast::GroupAttri<C> for TimingTableLookUp<C> {
703  #[inline]
704  #[expect(clippy::float_arithmetic)]
705  fn fmt_liberty<T: core::fmt::Write, I: ast::Indentation, K: core::fmt::Display>(
706    &self,
707    key: K,
708    f: &mut ast::CodeFormatter<'_, T, I>,
709  ) -> core::fmt::Result {
710    let chunk_size =
711      if self.index_2.is_empty() { self.values.len() } else { self.index_2.len() };
712    let len = self.values.len();
713    fmt_comment_liberty(Some(&self.comments), f)?;
714    DisplayTableLookUp {
715      name: &self.name,
716      index_1: &self.index_1,
717      index_2: &self.index_2,
718      sigma_type: None,
719      values: DisplayValues {
720        len,
721        chunk_size,
722        inner: self.values.iter().copied(),
723      },
724    }
725    .fmt_self::<_, _, C, _, _>("", &key, f)?;
726    if !self.lvf_moments_values.is_empty() {
727      DisplayTableLookUp {
728        name: &self.name,
729        index_1: &self.index_1,
730        index_2: &self.index_2,
731        sigma_type: None,
732        values: DisplayValues {
733          len,
734          chunk_size,
735          inner: izip!(self.values.iter(), self.lvf_moments_values.iter())
736            .map(|(value, lvf)| lvf.mean - value),
737        },
738      }
739      .fmt_self::<_, _, C, _, _>("ocv_mean_shift_", &key, f)?;
740      DisplayTableLookUp {
741        name: &self.name,
742        index_1: &self.index_1,
743        index_2: &self.index_2,
744        sigma_type: None,
745        values: DisplayValues {
746          len,
747          chunk_size,
748          inner: self.lvf_moments_values.iter().map(|lvf| lvf.std_dev),
749        },
750      }
751      .fmt_self::<_, _, C, _, _>("ocv_std_dev_", &key, f)?;
752      DisplayTableLookUp {
753        name: &self.name,
754        index_1: &self.index_1,
755        index_2: &self.index_2,
756        sigma_type: None,
757        values: DisplayValues {
758          len,
759          chunk_size,
760          inner: self.lvf_moments_values.iter().map(|lvf| lvf.skewness),
761        },
762      }
763      .fmt_self::<_, _, C, _, _>("ocv_skewness_", &key, f)?;
764    }
765    if !self.lvf_early_late_values.is_empty() {
766      DisplayTableLookUp {
767        name: &self.name,
768        index_1: &self.index_1,
769        index_2: &self.index_2,
770        sigma_type: Some(SigmaType::Early),
771        values: DisplayValues {
772          len,
773          chunk_size,
774          inner: self.lvf_early_late_values.iter().map(|lvf| lvf.early_sigma),
775        },
776      }
777      .fmt_self::<_, _, C, _, _>("ocv_sigma_", &key, f)?;
778      DisplayTableLookUp {
779        name: &self.name,
780        index_1: &self.index_1,
781        index_2: &self.index_2,
782        sigma_type: Some(SigmaType::Late),
783        values: DisplayValues {
784          len,
785          chunk_size,
786          inner: self.lvf_early_late_values.iter().map(|lvf| lvf.late_sigma),
787        },
788      }
789      .fmt_self::<_, _, C, _, _>("ocv_sigma_", &key, f)?;
790    }
791    Ok(())
792  }
793  fn nom_parse<'a, const IS_INCLUDED: bool>(
794    _: &mut Self::Builder,
795    _: &'a str,
796    _: &str,
797    _: &mut ast::ParseScope<'_>,
798  ) -> nom::IResult<&'a str, Result<(), ast::IdError>, nom::error::Error<&'a str>> {
799    unreachable!()
800  }
801}
802
803impl<C: 'static + Ctx> Group<C> for TimingTableLookUp<C> {}
804
805#[cfg(test)]
806mod test {
807  use super::{LVFEarlyLate, LVFMoments};
808
809  #[test]
810  #[expect(deprecated)]
811  fn lvf_convert() {
812    let nominal = 0.5065;
813    let mean_shift = 0.005352;
814    let std_dev = 0.04952;
815    let skewness = 0.03483;
816    let early_sigma = 0.0422;
817    let late_sigma = 0.0624;
818    let mean = nominal + mean_shift;
819    let moments = LVFMoments { mean, std_dev, skewness };
820    let early_late = LVFEarlyLate { early_sigma, late_sigma };
821    moments.to_early_late(nominal);
822    moments.to_early_late(nominal).unwrap().to_moments(nominal, mean);
823    early_late.to_moments(nominal, mean);
824  }
825}