verilogae_util/
lib.rs

1use numpy::{Element, PyArray1};
2use pyo3::exceptions::PyTypeError;
3use pyo3::prelude::pyclass;
4use pyo3::types::PyDict;
5use pyo3::{FromPyObject, PyAny, PyObject, PyResult};
6use std::convert::TryInto;
7use std::ffi::CString;
8use std::os::raw::c_char;
9
10pub enum Vectorized<'lt, T: Copy + Clone> {
11    Slice(&'lt [T]),
12    Scalar(T),
13}
14
15impl<'lt, T: Copy + Clone> Vectorized<'lt, T> {
16    #[allow(clippy::missing_safety_doc)]
17    #[inline]
18    pub unsafe fn get_unchecked(&self, index: usize) -> T {
19        match self {
20            Self::Scalar(res) => *res,
21            Self::Slice(values) => *values.get_unchecked(index),
22        }
23    }
24}
25
26impl<'lt, T: Copy + Clone + Element + FromPyObject<'lt> + 'lt> Vectorized<'lt, T> {
27    #[inline]
28    pub fn from_python(from: &'lt PyAny, name: &str, iterations: &mut usize) -> PyResult<Self> {
29        let res = if let Ok(val) = from.extract() {
30            Vectorized::Scalar(val)
31        } else if let Ok(values) = from.extract::<&PyArray1<T>>() {
32            // Save because we only keep the slice as a temporary variable
33            // The slice can not be mutated from rust and python doesn't run until the immutable view is dropped
34            // As such this does not cause UB here
35            match unsafe{values.as_slice()}.unwrap() {
36                [val] => Vectorized::Scalar(*val),
37                values if values.len() == *iterations => Vectorized::Slice(values),
38                values if *iterations == 1 => {
39                    *iterations = values.len();
40                    Vectorized::Slice(values)
41                },
42                values => return Err(pyo3::exceptions::PyTypeError::new_err(
43                    format!(
44                        "Arguments must have the same length or be scalars but '{}' has length {} while previous arguments had length {}",
45                        name,
46                        values.len(),
47                        *iterations
48                    )
49                )),
50            }
51        } else {
52            return Err(pyo3::exceptions::PyTypeError::new_err(format!(
53                "eval: Expected scalar {} value or 1d numpy {}-array for argument{}",
54                std::any::type_name::<T>(),
55                std::any::type_name::<T>(),
56                name,
57            )));
58        };
59        Ok(res)
60    }
61
62    #[inline]
63    pub fn from_dict(
64        dict: Option<&'lt PyDict>,
65        name: &str,
66        loc: &str,
67        iterations: &mut usize,
68    ) -> PyResult<Self> {
69        if let Some(dict) = dict {
70            if let Some(val) = dict.get_item(name) {
71                return Vectorized::from_python(val, name, iterations);
72            }
73        }
74        Err(pyo3::exceptions::PyTypeError::new_err(format!(
75            "eval: Required argument '{}' is missing from {}",
76            name, loc
77        )))
78    }
79}
80
81impl<'lt> Vectorized<'lt, f64> {
82    #[inline]
83    pub fn from_dict_opt(
84        dict: Option<&'lt PyDict>,
85        name: &str,
86        iterations: &mut usize,
87    ) -> PyResult<Self> {
88        if let Some(dict) = dict {
89            if let Some(val) = dict.get_item(name) {
90                return Vectorized::from_python(val, name, iterations);
91            }
92        }
93        Ok(Self::Scalar(0.0))
94    }
95}
96
97#[pyclass]
98pub struct NonNumericParameter {
99    #[pyo3(get)]
100    pub name: &'static str,
101    #[pyo3(get)]
102    pub description: &'static str,
103    #[pyo3(get)]
104    pub unit: &'static str,
105    #[pyo3(get)]
106    pub group: &'static str,
107
108    #[pyo3(get)]
109    pub default: PyObject,
110}
111
112#[pyclass]
113pub struct RealParameter {
114    #[pyo3(get)]
115    pub name: &'static str,
116    #[pyo3(get)]
117    pub description: &'static str,
118    #[pyo3(get)]
119    pub unit: &'static str,
120    #[pyo3(get)]
121    pub group: &'static str,
122
123    #[pyo3(get)]
124    pub default: f64,
125
126    #[pyo3(get)]
127    pub min: f64,
128    #[pyo3(get)]
129    pub max: f64,
130
131    #[pyo3(get)]
132    pub min_inclusive: bool,
133    #[pyo3(get)]
134    pub max_inclusive: bool,
135}
136
137#[pyclass]
138pub struct IntegerParameter {
139    #[pyo3(get)]
140    pub name: &'static str,
141    #[pyo3(get)]
142    pub description: &'static str,
143    #[pyo3(get)]
144    pub unit: &'static str,
145    #[pyo3(get)]
146    pub group: &'static str,
147
148    #[pyo3(get)]
149    pub default: i64,
150
151    #[pyo3(get)]
152    pub min: i64,
153    #[pyo3(get)]
154    pub max: i64,
155
156    #[pyo3(get)]
157    pub min_inclusive: bool,
158    #[pyo3(get)]
159    pub max_inclusive: bool,
160}
161
162#[repr(transparent)]
163pub struct PyFfiString(*mut c_char);
164
165impl<'source> FromPyObject<'source> for PyFfiString {
166    fn extract(ob: &'source PyAny) -> PyResult<Self> {
167        let string: &str = ob.extract()?;
168        let cstr = CString::new(string).map_err(PyTypeError::new_err)?;
169        Ok(Self(cstr.into_raw()))
170    }
171}
172
173impl Drop for PyFfiString {
174    fn drop(&mut self) {
175        unsafe {
176            CString::from_raw(self.0);
177        }
178    }
179}
180
181pub trait FFI {
182    type FfiTy;
183    fn into_ffi(self) -> Self::FfiTy;
184}
185
186impl FFI for PyFfiString {
187    type FfiTy = *const c_char;
188
189    #[inline(always)]
190    fn into_ffi(self) -> Self::FfiTy {
191        self.0
192    }
193}
194
195// Identity for primitives
196
197impl FFI for bool {
198    type FfiTy = bool;
199
200    #[inline(always)]
201    fn into_ffi(self) -> Self::FfiTy {
202        self
203    }
204}
205
206impl FFI for f64 {
207    type FfiTy = f64;
208
209    #[inline(always)]
210    fn into_ffi(self) -> Self::FfiTy {
211        self
212    }
213}
214
215impl FFI for i64 {
216    type FfiTy = i64;
217
218    #[inline(always)]
219    fn into_ffi(self) -> Self::FfiTy {
220        self
221    }
222}
223
224#[repr(transparent)]
225struct PyFfiPtr<A>(*mut A);
226
227impl<A> Drop for PyFfiPtr<A> {
228    fn drop(&mut self) {
229        unsafe {
230            Box::from_raw(self.0);
231        }
232    }
233}
234
235#[pyclass]
236pub struct OpVar {
237    #[pyo3(get)]
238    pub name: &'static str,
239    #[pyo3(get)]
240    pub description: &'static str,
241    #[pyo3(get)]
242    pub unit: &'static str,
243}
244
245// Arrays can't be passed directly over C-ABI instead pointers have to be used
246// For multidimensional arrays pointers to arrays are used
247
248macro_rules! array_ffi_impl(
249    ($($len:expr),*) => {
250        $(
251            impl<'a, T: FromPyObject<'a> > FromPyObject<'a> for PyFfiPtr<[T; $len]> {
252                fn extract(ob: &'a PyAny) -> PyResult<Self> {
253                    let data: Vec<T> = ob.extract()?;
254                    let len = data.len();
255                    let data: Box<[T; $len]> = data.into_boxed_slice().try_into().map_err(|_|PyTypeError::new_err(format!("Expected array of size {} but found {}",$len,len)))?;
256                    Ok(Self(Box::into_raw(data)))
257                }
258            }
259
260            impl<T: FFI> FFI for PyFfiPtr<[T; $len]> {
261                type FfiTy = * const T::FfiTy;
262
263                fn into_ffi(self) -> Self::FfiTy {
264                    self.0 as *const T::FfiTy
265                }
266            }
267
268            impl<T: FFI> FFI for [T; $len] {
269                type FfiTy = Self;
270
271                fn into_ffi(self) -> Self::FfiTy {
272                    self
273                }
274            }
275        )*
276    }
277);
278
279array_ffi_impl!(
280    32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9,
281    8, 7, 6, 5, 4, 3, 2, 1, 0
282);