feat!(pyargus): simplify the API surface
- Get rid of helper functions. It is not that much more verbose to create signals with `argus.FloatSignal(...)` than `argus.signal(..., dtype=argus.dtype.float64`). - Make the package hierarchy flat: everything is under `argus`. If this is an issue, it can be changed in the future. - Add type hints for interval types.
This commit is contained in:
parent
3714cd5936
commit
d39e3d3e12
14 changed files with 237 additions and 247 deletions
|
|
@ -115,11 +115,11 @@ impl ConstInt {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a constant _unsigned_ integer expression
|
||||
/// Create a constant *unsigned* integer expression
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// Negating an unsigned integer during evaluation _may_ lead to the evaluation method
|
||||
/// Negating an unsigned integer during evaluation *may* lead to the evaluation method
|
||||
/// panicking.
|
||||
#[pyclass(extends=PyNumExpr, module = "argus")]
|
||||
pub struct ConstUInt;
|
||||
|
|
@ -162,7 +162,7 @@ impl VarInt {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create an _unsigned_ integer variable
|
||||
/// Create an *unsigned* integer variable
|
||||
#[pyclass(extends=PyNumExpr, module = "argus")]
|
||||
pub struct VarUInt;
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ fn parse_expr(expr_str: &str) -> PyResult<PyObject> {
|
|||
fn pyargus(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
pyo3_log::init();
|
||||
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
m.add("__version__", version)?;
|
||||
m.add_class::<DType>()?;
|
||||
m.add_function(wrap_pyfunction!(parse_expr, m)?)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@ use std::str::FromStr;
|
|||
|
||||
use argus::signals::interpolation::{Constant, Linear};
|
||||
use argus::{AnySignal, BooleanSemantics, QuantitativeSemantics, Signal, Trace};
|
||||
use pyo3::exceptions::PyTypeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyString};
|
||||
|
||||
use crate::expr::PyBoolExpr;
|
||||
use crate::signals::{BoolSignal, FloatSignal, PyInterp, PySignal, SignalKind};
|
||||
use crate::PyArgusError;
|
||||
|
||||
/// A signal trace to pass evaluate.
|
||||
///
|
||||
/// To evaluate the robustness of a set of signals, we need to construct a `Trace`
|
||||
/// containing the signals.
|
||||
///
|
||||
/// :param signals: A dictionary mapping signal names to `argus.Signal` types.
|
||||
/// :type signals: dict[str, argus.Signal]
|
||||
#[pyclass(name = "Trace", module = "argus")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PyTrace {
|
||||
|
|
@ -20,21 +25,8 @@ pub struct PyTrace {
|
|||
#[pymethods]
|
||||
impl PyTrace {
|
||||
#[new]
|
||||
fn new(dict: &PyDict) -> PyResult<Self> {
|
||||
let mut signals = HashMap::with_capacity(dict.len());
|
||||
for (key, val) in dict {
|
||||
let key: &PyString = key
|
||||
.downcast()
|
||||
.map_err(|e| PyTypeError::new_err(format!("expected dictionary with string keys for trace ({})", e)))?;
|
||||
let val: &PyCell<PySignal> = val.downcast().map_err(|e| {
|
||||
PyTypeError::new_err(format!(
|
||||
"expected `argus.Signal` value for key `{}` in trace ({})",
|
||||
key, e
|
||||
))
|
||||
})?;
|
||||
let signal = val.borrow().signal.clone();
|
||||
signals.insert(key.to_string(), signal);
|
||||
}
|
||||
fn new(signals: HashMap<String, PySignal>) -> PyResult<Self> {
|
||||
let signals = signals.into_iter().map(|(k, v)| (k, v.signal)).collect();
|
||||
|
||||
Ok(Self { signals })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ use std::str::FromStr;
|
|||
|
||||
use argus::signals::interpolation::{Constant, Linear};
|
||||
use argus::signals::Signal;
|
||||
use pyo3::exceptions::PyValueError;
|
||||
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyType;
|
||||
|
||||
use crate::{DType, PyArgusError};
|
||||
|
||||
#[pyclass(name = "InterpolationMethod", module = "argus")]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub enum PyInterp {
|
||||
pub(crate) enum PyInterp {
|
||||
#[default]
|
||||
Linear,
|
||||
Constant,
|
||||
|
|
@ -60,7 +59,7 @@ pub struct PySignal {
|
|||
}
|
||||
|
||||
impl PySignal {
|
||||
pub fn new<T>(signal: T, interpolation: PyInterp) -> Self
|
||||
pub(crate) fn new<T>(signal: T, interpolation: PyInterp) -> Self
|
||||
where
|
||||
T: Into<SignalKind>,
|
||||
{
|
||||
|
|
@ -128,6 +127,60 @@ impl PySignal {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new empty signal
|
||||
#[new]
|
||||
#[pyo3(signature = (*, interpolation_method = "linear"))]
|
||||
fn init(interpolation_method: &str) -> PyResult<Self> {
|
||||
_ = interpolation_method;
|
||||
Err(PyNotImplementedError::new_err(
|
||||
"cannot directly construct an abstract Signal",
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new signal with constant value
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (value, *, interpolation_method = "linear"))]
|
||||
fn constant(_: &PyType, _py: Python<'_>, value: &PyAny, interpolation_method: &str) -> PyResult<Py<Self>> {
|
||||
_ = value;
|
||||
_ = interpolation_method;
|
||||
Err(PyNotImplementedError::new_err(
|
||||
"cannot directly construct an abstract Signal",
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new signal from some finite number of samples
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (samples, *, interpolation_method = "linear"))]
|
||||
fn from_samples(_: &PyType, samples: Vec<(f64, &PyAny)>, interpolation_method: &str) -> PyResult<Py<Self>> {
|
||||
_ = samples;
|
||||
_ = interpolation_method;
|
||||
Err(PyNotImplementedError::new_err(
|
||||
"cannot directly construct an abstract Signal",
|
||||
))
|
||||
}
|
||||
|
||||
/// Push a new sample into the given signal.
|
||||
#[pyo3(signature = (time, value))]
|
||||
fn push(_: PyRefMut<'_, Self>, time: f64, value: &PyAny) -> PyResult<()> {
|
||||
_ = time;
|
||||
_ = value;
|
||||
Err(PyNotImplementedError::new_err(
|
||||
"cannot push samples to an abstract Signal",
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the value of the signal at the given time point.
|
||||
///
|
||||
/// If there exists a sample, then the value is returned, otherwise the value is
|
||||
/// interpolated. If the time point lies outside of the domain of the signal, then
|
||||
/// `None` is returned.
|
||||
fn at(_self_: PyRef<'_, Self>, time: f64) -> PyResult<Option<&PyAny>> {
|
||||
_ = time;
|
||||
Err(PyNotImplementedError::new_err(
|
||||
"cannot query for samples in an abstract Signal",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_signals {
|
||||
|
|
@ -142,7 +195,7 @@ macro_rules! impl_signals {
|
|||
/// Create a new empty signal
|
||||
#[new]
|
||||
#[pyo3(signature = (*, interpolation_method = "linear"))]
|
||||
fn new(interpolation_method: &str) -> PyResult<(Self, PySignal)> {
|
||||
fn init(interpolation_method: &str) -> PyResult<(Self, PySignal)> {
|
||||
let interp = PyInterp::from_str(interpolation_method)?;
|
||||
Ok((Self, PySignal::new(Signal::<$ty>::new(), interp)))
|
||||
}
|
||||
|
|
@ -181,6 +234,14 @@ macro_rules! impl_signals {
|
|||
fn push(mut self_: PyRefMut<'_, Self>, time: f64, value: $ty) -> Result<(), PyArgusError> {
|
||||
let super_: &mut PySignal = self_.as_mut();
|
||||
let signal: &mut Signal<$ty> = (&mut super_.signal).try_into().unwrap();
|
||||
// if it is an empty signal, make it sampled. Otherwise, throw an error.
|
||||
let signal: &mut Signal<$ty> = match signal {
|
||||
Signal::Empty => {
|
||||
super_.signal = Signal::<$ty>::new_with_capacity(1).into();
|
||||
(&mut super_.signal).try_into().unwrap()
|
||||
}
|
||||
_ => signal,
|
||||
};
|
||||
signal.push(core::time::Duration::from_secs_f64(time), value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue