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 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
195impl 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
245macro_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);