use super::*;
use crate::evaluate::{FunctionRegistrationOptions, InliningPolicy};
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(from_py_object, name = "FunctionDefinition", module = "symbolica.core")]
#[derive(Clone)]
pub struct PythonFunctionDefinition {
function: PolyVariable,
arguments: Vec<PolyVariable>,
body: Atom,
options: FunctionRegistrationOptions,
}
impl PythonFunctionDefinition {
pub(super) fn register(self, function_map: &mut FunctionMap) -> PyResult<()> {
let Self {
function,
arguments,
body,
options,
} = self;
let arguments = arguments
.into_iter()
.map(|argument| match argument {
PolyVariable::Symbol(symbol) => Ok(Indeterminate::Symbol(symbol, symbol.into())),
PolyVariable::Function(symbol, function) => {
Ok(Indeterminate::Function(symbol, function))
}
_ => Err(exceptions::PyValueError::new_err(format!(
"Bad function argument {argument} in function {function}",
))),
})
.collect::<Result<Vec<_>, _>>()?;
match function {
PolyVariable::Symbol(symbol) => {
function_map.add_function_with_options(symbol, arguments, body, options)
}
PolyVariable::Function(symbol, function) => {
let tags = function
.as_fun_view()
.unwrap()
.iter()
.map(|tag| tag.to_owned())
.collect();
function_map
.add_tagged_function_with_options(symbol, tags, arguments, body, options)
}
_ => {
return Err(exceptions::PyValueError::new_err(format!(
"Expected function name instead of {function:?}",
)));
}
}
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))
}
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonFunctionDefinition {
#[new]
#[pyo3(signature = (function, arguments, body, inlining = "auto"))]
fn new(
function: PolyVariable,
arguments: Vec<PolyVariable>,
body: PythonExpression,
inlining: &str,
) -> PyResult<Self> {
let inlining = match inlining {
"always" => InliningPolicy::Always,
"never" => InliningPolicy::Never,
"auto" => InliningPolicy::Auto,
_ => {
return Err(exceptions::PyValueError::new_err(format!(
"Unknown inlining policy '{inlining}'; expected 'always', 'never', or 'auto'",
)));
}
};
Ok(Self {
function,
arguments,
body: body.expr,
options: FunctionRegistrationOptions::new().inlining(inlining),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{parse, symbol};
#[test]
fn python_function_definition_registers_non_inline_evaluator() {
let function = symbol!("symbolica::python::function_definition");
let definition = PythonFunctionDefinition::new(
PolyVariable::try_from(parse!("symbolica::python::function_definition(1)")).unwrap(),
vec![symbol!("y").into()],
parse!("y^2").into(),
"never",
)
.unwrap();
let mut function_map = FunctionMap::new();
definition.register(&mut function_map).unwrap();
let evaluator = parse!("symbolica::python::function_definition(1, x)")
.evaluator(&[parse!("x")])
.function_map(function_map)
.build()
.unwrap();
let exported = evaluator
.clone()
.map_coeff(&|coefficient| coefficient.re.to_f64())
.export_instructions();
assert_eq!(exported.sub_evaluators.len(), 1);
assert_eq!(exported.sub_evaluators[0].symbol, function);
assert_eq!(exported.sub_evaluators[0].tags, ["1"]);
assert!(exported.instructions.iter().any(|instruction| {
matches!(
instruction,
Instruction::Fun(_, function, _)
if function.0 == exported.sub_evaluators[0].symbol
&& function.1 == ["1"]
&& function.2.len() == 1
)
}));
assert_eq!(exported.sub_evaluators[0].input_count, 1);
let mut evaluator = evaluator.map_coeff(&|coefficient| coefficient.re.to_f64());
let mut output = [0.];
evaluator.evaluate(&[3.], &mut output);
assert_eq!(output, [9.]);
}
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "EvaluatorInstructions",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonEvaluatorInstructions {
input_count: usize,
output_count: usize,
instructions: Vec<Instruction>,
temporary_count: usize,
constants: Vec<Complex<Rational>>,
sub_evaluators: Vec<PythonEvaluatorFunction>,
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(from_py_object, name = "EvaluatorFunction", module = "symbolica.core")]
#[derive(Clone)]
pub struct PythonEvaluatorFunction {
function: Symbol,
tags: Vec<String>,
evaluator: PythonEvaluatorInstructions,
}
impl From<ExportedInstructions<Complex<Rational>>> for PythonEvaluatorInstructions {
fn from(exported: ExportedInstructions<Complex<Rational>>) -> Self {
Self {
input_count: exported.input_count,
output_count: exported.output_count,
instructions: exported.instructions,
temporary_count: exported.temporary_count,
constants: exported.constants,
sub_evaluators: exported
.sub_evaluators
.into_iter()
.map(PythonEvaluatorFunction::from)
.collect(),
}
}
}
impl From<ExportedSubEvaluator<Complex<Rational>>> for PythonEvaluatorFunction {
fn from(sub_evaluator: ExportedSubEvaluator<Complex<Rational>>) -> Self {
Self {
function: sub_evaluator.symbol,
tags: sub_evaluator.tags,
evaluator: sub_evaluator.instructions.into(),
}
}
}
impl PythonEvaluatorInstructions {
fn instruction_tuples<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyTuple>>> {
fn slot_to_object(slot: &Slot) -> (&str, usize) {
match slot {
Slot::Const(x) => ("const", *x),
Slot::Param(x) => ("param", *x),
Slot::Temp(x) => ("temp", *x),
Slot::Out(x) => ("out", *x),
}
}
let mut result = vec![];
for instruction in &self.instructions {
match instruction {
Instruction::Add(out, args, real_args) | Instruction::Mul(out, args, real_args) => {
result.push(PyTuple::new(
py,
[
if matches!(instruction, Instruction::Add(_, _, _)) {
"add"
} else {
"mul"
}
.into_pyobject(py)?
.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
args.iter()
.map(slot_to_object)
.collect::<Vec<_>>()
.into_pyobject(py)?
.as_any(),
real_args.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Pow(out, base, exponent, is_real) => {
result.push(PyTuple::new(
py,
[
"pow".into_pyobject(py)?.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
slot_to_object(base).into_pyobject(py)?.as_any(),
exponent.into_pyobject(py)?.as_any(),
is_real.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Powf(out, base, exponent, is_real) => {
result.push(PyTuple::new(
py,
[
"powf".into_pyobject(py)?.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
slot_to_object(base).into_pyobject(py)?.as_any(),
slot_to_object(exponent).into_pyobject(py)?.as_any(),
is_real.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Fun(out, function, is_real) => {
let (name, tags, args) = &**function;
result.push(PyTuple::new(
py,
[
"fun".into_pyobject(py)?.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
PythonExpression::from(Atom::var(*name))
.into_pyobject(py)?
.as_any(),
tags.into_pyobject(py)?.as_any(),
args.iter()
.map(slot_to_object)
.collect::<Vec<_>>()
.into_pyobject(py)?
.as_any(),
is_real.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Assign(out, input) => {
result.push(PyTuple::new(
py,
[
"assign".into_pyobject(py)?.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
slot_to_object(input).into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::IfElse(condition, label) => {
result.push(PyTuple::new(
py,
[
"if_else".into_pyobject(py)?.as_any(),
slot_to_object(condition).into_pyobject(py)?.as_any(),
label.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Join(out, condition, if_true, if_false) => {
result.push(PyTuple::new(
py,
[
"join".into_pyobject(py)?.as_any(),
slot_to_object(out).into_pyobject(py)?.as_any(),
slot_to_object(condition).into_pyobject(py)?.as_any(),
slot_to_object(if_true).into_pyobject(py)?.as_any(),
slot_to_object(if_false).into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Goto(label) => {
result.push(PyTuple::new(
py,
[
"goto".into_pyobject(py)?.as_any(),
label.into_pyobject(py)?.as_any(),
],
)?);
}
Instruction::Label(label) => {
result.push(PyTuple::new(
py,
[
"label".into_pyobject(py)?.as_any(),
label.into_pyobject(py)?.as_any(),
],
)?);
}
}
}
Ok(result)
}
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonEvaluatorInstructions {
#[getter]
fn input_count(&self) -> usize {
self.input_count
}
#[getter]
fn output_count(&self) -> usize {
self.output_count
}
#[getter]
#[gen_stub(override_return_type(type_repr = "list[tuple]"))]
fn instructions<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyTuple>>> {
self.instruction_tuples(py)
}
#[getter]
fn temporary_count(&self) -> usize {
self.temporary_count
}
#[getter]
fn constants(&self) -> Vec<PythonExpression> {
self.constants
.iter()
.map(|constant| Atom::num(constant.clone()).into())
.collect()
}
#[getter]
fn sub_evaluators(&self) -> Vec<PythonEvaluatorFunction> {
self.sub_evaluators.clone()
}
fn __repr__(&self) -> String {
format!(
"EvaluatorInstructions(input_count={}, output_count={}, instructions={}, temporary_count={}, constants={}, sub_evaluators={})",
self.input_count,
self.output_count,
self.instructions.len(),
self.temporary_count,
self.constants.len(),
self.sub_evaluators.len()
)
}
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonEvaluatorFunction {
#[getter]
fn function(&self) -> PythonExpression {
Atom::var(self.function).into()
}
#[getter]
fn tags(&self) -> Vec<String> {
self.tags.clone()
}
#[getter]
fn evaluator(&self) -> PythonEvaluatorInstructions {
self.evaluator.clone()
}
fn __repr__(&self) -> String {
format!(
"EvaluatorFunction(function={}, tags={:?})",
Atom::var(self.function),
self.tags
)
}
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(from_py_object, name = "Evaluator", module = "symbolica.core")]
#[derive(Clone)]
pub struct PythonExpressionEvaluator {
pub rational_constants: Vec<Complex<Rational>>,
pub eval_complex: ExpressionEvaluator<Complex<f64>>,
pub eval_real: Option<ExpressionEvaluator<f64>>,
#[cfg(feature = "native_code_generation")]
pub jit_real: Option<JITCompiledEvaluator<f64>>,
#[cfg(feature = "native_code_generation")]
pub jit_complex: Option<JITCompiledEvaluator<Complex<f64>>>,
pub eval_double_float: Option<ExpressionEvaluator<DoubleFloat>>,
pub eval_double_float_complex: Option<ExpressionEvaluator<Complex<DoubleFloat>>>,
pub eval_arb_prec: Option<(u32, ExpressionEvaluator<Float>)>,
pub eval_arb_prec_complex: Option<(u32, ExpressionEvaluator<Complex<Float>>)>,
pub jit_compile: bool,
#[cfg(feature = "native_code_generation")]
pub jit_settings: JITCompilationSettings,
}
impl PythonExpressionEvaluator {
fn evaluate_double_float<'py>(
&mut self,
inputs: Vec<PythonMultiPrecisionFloat>,
) -> PyResult<Vec<PythonMultiPrecisionFloat>> {
if self.rational_constants.iter().any(|c| !c.is_real()) {
return Err(exceptions::PyValueError::new_err(
"Evaluator contains complex coefficients. Use evaluate_complex instead.",
));
}
if self.eval_double_float.is_none() {
self.eval_double_float = Some(
self.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.map_coeff(&|x| (&x.re).into()),
);
}
let eval = &mut self.eval_double_float.as_mut().unwrap();
let inputs = inputs
.into_iter()
.map(|x| x.0.to_double_float())
.collect::<Vec<_>>();
let mut out = vec![0f64.into(); self.eval_complex.get_output_len()];
eval.try_evaluate(&inputs, &mut out)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
Ok(out.into_iter().map(|x| Float::from(x).into()).collect())
}
fn evaluate_double_float_complex(
&mut self,
inputs: Vec<PythonMultiPrecisionComplex>,
) -> PyResult<Vec<PythonMultiPrecisionComplex>> {
if self.eval_double_float_complex.is_none() {
self.eval_double_float_complex = Some(
self.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.map_coeff(&|x| Complex::new((&x.re).into(), (&x.im).into())),
);
}
let eval = &mut self.eval_double_float_complex.as_mut().unwrap();
let inputs = inputs
.into_iter()
.map(|x| Complex::new(x.0.re.to_double_float(), x.0.im.to_double_float()))
.collect::<Vec<_>>();
let mut out =
vec![Complex::from(DoubleFloat::from(0.)); self.eval_complex.get_output_len()];
eval.try_evaluate(&inputs, &mut out)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
Ok(out
.into_iter()
.map(|x| {
PythonMultiPrecisionComplex(Complex::new(Float::from(x.re), Float::from(x.im)))
})
.collect())
}
}
fn reshape_evaluator_inputs<T: Clone>(
arr: CowArray<'_, T, IxDyn>,
input_len: usize,
) -> PyResult<CowArray<'_, T, IxDyn>> {
let arr = if arr.shape().len() == 1 {
let orig_len = arr.len();
if input_len == 0 {
if arr.is_empty() {
arr.into_shape_with_order((1, 0))
.map_err(|_| {
exceptions::PyValueError::new_err(format!(
"Failed to reshape input array. Expected (_, {}), but got {:?}",
input_len,
[orig_len],
))
})?
.into_dyn()
} else {
return Err(exceptions::PyValueError::new_err(format!(
"Input length mismatch: expected (_, {}), but got {:?}",
input_len,
[orig_len],
)));
}
} else {
let target_shape = (orig_len / input_len, input_len);
let arr = if !arr.is_standard_layout() {
CowArray::from(arr.as_standard_layout().into_owned())
} else {
arr
};
arr.into_shape_with_order(target_shape)
.map_err(|_| {
exceptions::PyValueError::new_err(format!(
"Failed to reshape input array. Expected (_, {}), but got {:?}",
input_len,
[orig_len],
))
})?
.into_dyn()
}
} else {
arr
};
if arr.shape().len() != 2 || arr.shape()[1] != input_len {
return Err(exceptions::PyValueError::new_err(format!(
"Input length mismatch: expected (_, {}), but got {:?}",
input_len,
arr.shape(),
)));
}
Ok(arr)
}
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonExpressionEvaluator {
pub fn __copy__(&self) -> PythonExpressionEvaluator {
PythonExpressionEvaluator {
rational_constants: self.rational_constants.clone(),
eval_complex: self.eval_complex.clone(),
eval_real: self.eval_real.clone(),
#[cfg(feature = "native_code_generation")]
jit_real: self.jit_real.clone(),
#[cfg(feature = "native_code_generation")]
jit_complex: self.jit_complex.clone(),
eval_double_float: self.eval_double_float.clone(),
eval_double_float_complex: self.eval_double_float_complex.clone(),
eval_arb_prec: self.eval_arb_prec.clone(),
eval_arb_prec_complex: self.eval_arb_prec_complex.clone(),
jit_compile: self.jit_compile,
#[cfg(feature = "native_code_generation")]
jit_settings: self.jit_settings.clone(),
}
}
#[pyo3(signature = (jit_compile, direct_translation = None, optimization_level = None, options = None))]
fn jit_compile(
&mut self,
jit_compile: bool,
direct_translation: Option<bool>,
optimization_level: Option<u8>,
options: Option<HashMap<String, String>>,
) -> PyResult<()> {
#[cfg(not(feature = "native_code_generation"))]
{
let _ = (direct_translation, optimization_level, options);
self.jit_compile = false;
if jit_compile {
return Err(exceptions::PyRuntimeError::new_err(
"JIT compilation is not available in this Symbolica build.",
));
}
return Ok(());
}
#[cfg(feature = "native_code_generation")]
{
self.jit_compile = jit_compile;
self.jit_real = None;
self.jit_complex = None;
let mut jit_settings = self.jit_settings.clone();
if let Some(direct_translation) = direct_translation {
jit_settings = jit_settings.direct_translation(direct_translation);
}
if let Some(optimization_level) = optimization_level {
jit_settings = jit_settings.optimization_level(optimization_level);
}
if let Some(options) = options {
for (key, value) in options {
jit_settings = jit_settings.with_option(key, value);
}
}
self.jit_settings = jit_settings;
Ok(())
}
}
#[classmethod]
fn load(_cls: &Bound<'_, PyType>, evaluator: Bound<'_, PyBytes>) -> PyResult<Self> {
#[cfg(not(feature = "native_code_generation"))]
{
let bytes: &[u8] = evaluator.extract()?;
let eval = bincode::decode_from_slice::<ExpressionEvaluator<Complex<Rational>>, _>(
bytes,
bincode::config::standard(),
)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?
.0;
return Ok(PythonExpressionEvaluator {
rational_constants: eval.get_constants().to_vec(),
eval_complex: eval.map_coeff(&|c| Complex::new(c.re.to_f64(), c.im.to_f64())),
eval_real: None,
eval_double_float: None,
eval_double_float_complex: None,
eval_arb_prec: None,
eval_arb_prec_complex: None,
jit_compile: false,
});
}
#[cfg(feature = "native_code_generation")]
{
type SavedEvaluator = (
bool,
JITCompilationSettings,
ExpressionEvaluator<Complex<Rational>>,
Option<JITCompiledEvaluator<f64>>,
Option<JITCompiledEvaluator<Complex<f64>>>,
);
type LegacySavedEvaluator = (
bool,
ExpressionEvaluator<Complex<Rational>>,
Option<JITCompiledEvaluator<f64>>,
Option<JITCompiledEvaluator<Complex<f64>>>,
);
let bytes: &[u8] = evaluator.extract()?;
let (jit_compile, jit_settings, eval, jit_real, jit_complex) =
match bincode::decode_from_slice::<SavedEvaluator, _>(
bytes,
bincode::config::standard(),
) {
Ok((decoded, _)) => decoded,
Err(new_err) => {
let (jit_compile, eval, jit_real, jit_complex) =
bincode::decode_from_slice::<LegacySavedEvaluator, _>(
bytes,
bincode::config::standard(),
)
.map_err(|_| pyo3::exceptions::PyIOError::new_err(new_err.to_string()))?
.0;
(
jit_compile,
JITCompilationSettings::default(),
eval,
jit_real,
jit_complex,
)
}
};
Ok(PythonExpressionEvaluator {
rational_constants: eval.get_constants().to_vec(),
eval_complex: eval.map_coeff(&|c| Complex::new(c.re.to_f64(), c.im.to_f64())),
eval_real: None,
jit_real,
jit_complex,
eval_double_float: None,
eval_double_float_complex: None,
eval_arb_prec: None,
eval_arb_prec_complex: None,
jit_compile,
jit_settings,
})
}
}
fn save<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyBytes>> {
#[cfg(not(feature = "native_code_generation"))]
{
return bincode::encode_to_vec(
self.eval_complex
.clone()
.set_coeff(&self.rational_constants),
bincode::config::standard(),
)
.map(|a| PyBytes::new(py, &a))
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()));
}
#[cfg(feature = "native_code_generation")]
{
bincode::encode_to_vec(
&(
self.jit_compile,
self.jit_settings.clone(),
self.eval_complex
.clone()
.set_coeff(&self.rational_constants),
&self.jit_real,
&self.jit_complex,
),
bincode::config::standard(),
)
.map(|a| PyBytes::new(py, &a))
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))
}
}
#[cfg(feature = "native_code_generation")]
#[pyo3(signature = (complex = false))]
fn export_symjit<'py>(&self, py: Python<'py>, complex: bool) -> PyResult<Bound<'py, PyBytes>> {
if !self.jit_compile {
return Err(exceptions::PyRuntimeError::new_err(
"Cannot export a SymJIT application because this evaluator is not configured for JIT compilation.",
));
}
if complex {
let evaluator = self.jit_complex.as_ref().ok_or_else(|| {
exceptions::PyRuntimeError::new_err(
"Cannot export a SymJIT application because the JIT payload has not been compiled. Call evaluate_complex() once before exporting it.",
)
})?;
if evaluator.has_external_functions() {
return Err(exceptions::PyRuntimeError::new_err(
"Cannot export a self-contained SymJIT application because the selected JIT payload uses external functions.",
));
}
Ok(PyBytes::new(py, evaluator.as_bytes()))
} else {
let evaluator = self.jit_real.as_ref().ok_or_else(|| {
exceptions::PyRuntimeError::new_err(
"Cannot export a SymJIT application because the JIT payload has not been compiled. Call evaluate_double() once before exporting it.",
)
})?;
if evaluator.has_external_functions() {
return Err(exceptions::PyRuntimeError::new_err(
"Cannot export a self-contained SymJIT application because the selected JIT payload uses external functions.",
));
}
Ok(PyBytes::new(py, evaluator.as_bytes()))
}
}
fn get_instructions(&self) -> PythonEvaluatorInstructions {
self.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.export_instructions()
.into()
}
#[pyo3(signature = (other, cpe_iterations = None))]
fn merge(
&mut self,
other: PythonExpressionEvaluator,
cpe_iterations: Option<usize>,
) -> PyResult<()> {
let mut r = self
.eval_complex
.clone()
.set_coeff(&self.rational_constants);
r.merge(
other
.eval_complex
.clone()
.set_coeff(&other.rational_constants),
cpe_iterations,
)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Could not merge evaluators: {e}",))
})?;
self.rational_constants = r.get_constants().to_vec();
self.eval_complex = r.map_coeff(&|c| Complex::new(c.re.to_f64(), c.im.to_f64()));
self.eval_real = None;
#[cfg(feature = "native_code_generation")]
{
self.jit_real = None;
self.jit_complex = None;
}
self.eval_arb_prec = None;
self.eval_arb_prec_complex = None;
Ok(())
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.float64]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, f64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
if self.rational_constants.iter().any(|c| !c.is_real()) {
return Err(exceptions::PyValueError::new_err(
"Evaluator contains complex coefficients. Use evaluate_complex_flat instead.",
));
}
#[cfg(feature = "native_code_generation")]
let use_jit = self.jit_compile;
#[cfg(not(feature = "native_code_generation"))]
let use_jit = false;
#[cfg(feature = "native_code_generation")]
if use_jit && self.jit_real.is_none() {
let real_eval = self.eval_complex.clone().map_coeff(&|x| x.re);
self.jit_real = Some(
real_eval
.jit_compile(self.jit_settings.clone())
.map_err(|e| exceptions::PyValueError::new_err(e.to_string()))?,
);
}
if !use_jit && self.eval_real.is_none() {
self.eval_real = Some(self.eval_complex.clone().map_coeff(&|x| x.re));
}
let arr = reshape_evaluator_inputs(
CowArray::from(inputs.as_array()),
self.eval_complex.get_input_len(),
)?;
let n_inputs = arr.shape()[0];
let arr = if use_jit && n_inputs > 1 && !arr.is_standard_layout() {
CowArray::from(arr.as_standard_layout().into_owned())
} else {
arr
};
let mut out = ArrayD::zeros(&[n_inputs, self.eval_complex.get_output_len()][..]);
#[cfg(feature = "native_code_generation")]
if use_jit {
let eval = self.jit_real.as_mut().unwrap();
if n_inputs > 1 {
eval.batch_evaluate(
arr.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
n_inputs,
);
} else {
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
eval.evaluate(
i.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
);
}
}
return Ok(out.into_pyarray(py));
}
let eval = self.eval_real.as_mut().unwrap();
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
eval.try_evaluate(
i.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
}
Ok(out.into_pyarray(py))
}
fn evaluate_with_prec<'py>(
&mut self,
inputs: Vec<PythonMultiPrecisionFloat>,
decimal_digit_precision: u32,
) -> PyResult<Vec<PythonMultiPrecisionFloat>> {
if decimal_digit_precision == 32 {
return self.evaluate_double_float(inputs);
}
let prec = Float::decimal_digits_to_bits(decimal_digit_precision as f64)
.map_err(exceptions::PyValueError::new_err)?;
if self.rational_constants.iter().any(|c| !c.is_real()) {
return Err(exceptions::PyValueError::new_err(
"Evaluator contains complex coefficients. Use evaluate_complex instead.",
));
}
if self
.eval_arb_prec
.as_ref()
.is_none_or(|(cached_prec, _)| *cached_prec != prec)
{
self.eval_arb_prec = Some((
prec,
self.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.map_coeff_with_prec(&|x| x.re.to_multi_prec_float(prec), prec),
));
}
let eval = &mut self.eval_arb_prec.as_mut().unwrap().1;
let inputs = inputs.into_iter().map(|x| x.0).collect::<Vec<_>>();
let mut out = vec![Float::with_val(prec, 0); self.eval_complex.get_output_len()];
eval.try_evaluate(&inputs, &mut out)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
Ok(out.into_iter().map(|x| x.into()).collect())
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.complex128]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate_complex<'py>(
&mut self,
py: Python<'py>,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, Complex64, AllowTypeChange>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
#[cfg(feature = "native_code_generation")]
if self.jit_compile && self.jit_complex.is_none() {
self.jit_complex = Some(
self.eval_complex
.jit_compile(self.jit_settings.clone())
.map_err(|e| exceptions::PyValueError::new_err(e.to_string()))?,
);
}
let arr = reshape_evaluator_inputs(
CowArray::from(inputs.as_array()),
self.eval_complex.get_input_len(),
)?;
let n_inputs = arr.shape()[0];
#[cfg(feature = "native_code_generation")]
let use_jit = self.jit_compile;
#[cfg(not(feature = "native_code_generation"))]
let use_jit = false;
let arr = if use_jit && n_inputs > 1 && !arr.is_standard_layout() {
CowArray::from(arr.as_standard_layout().into_owned())
} else {
arr
};
let mut out = ArrayD::zeros(&[n_inputs, self.eval_complex.get_output_len()][..]);
#[cfg(feature = "native_code_generation")]
if use_jit {
let eval = self.jit_complex.as_mut().unwrap();
if n_inputs > 1 {
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(
arr.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
};
eval.batch_evaluate(sc, os, n_inputs);
} else {
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(
i.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err(
"Failed to convert input to slice",
)
})?,
)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err(
"Failed to convert output to slice",
)
})?,
)
};
eval.evaluate(sc, os);
}
}
return Ok(out.into_pyarray(py));
}
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(i.as_slice().ok_or_else(
|| exceptions::PyValueError::new_err("Failed to convert input to slice"),
)?)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
};
self.eval_complex
.try_evaluate(sc, os)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
}
Ok(out.into_pyarray(py))
}
fn evaluate_complex_with_prec<'py>(
&mut self,
inputs: Vec<PythonMultiPrecisionComplex>,
decimal_digit_precision: u32,
) -> PyResult<Vec<PythonMultiPrecisionComplex>> {
if decimal_digit_precision == 32 {
return self.evaluate_double_float_complex(inputs);
}
let prec = Float::decimal_digits_to_bits(decimal_digit_precision as f64)
.map_err(exceptions::PyValueError::new_err)?;
if self
.eval_arb_prec_complex
.as_ref()
.is_none_or(|(cached_prec, _)| *cached_prec != prec)
{
self.eval_arb_prec_complex = Some((
prec,
self.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.map_coeff_with_prec(
&|x| {
Complex::new(
x.re.to_multi_prec_float(prec),
x.im.to_multi_prec_float(prec),
)
},
prec,
),
));
}
let eval = &mut self.eval_arb_prec_complex.as_mut().unwrap().1;
let inputs = inputs.into_iter().map(|x| x.0).collect::<Vec<_>>();
let mut out = vec![
Complex::new(Float::with_val(prec, 0), Float::with_val(prec, 0));
self.eval_complex.get_output_len()
];
eval.try_evaluate(&inputs, &mut out)
.map_err(|error| exceptions::PyValueError::new_err(error.to_string()))?;
Ok(out.into_iter().map(PythonMultiPrecisionComplex).collect())
}
#[pyo3(signature = (dual_shape, zero_components = Vec::new()))]
fn dualize(
&mut self,
dual_shape: Vec<Vec<usize>>,
zero_components: Vec<(usize, usize)>,
) -> PyResult<()> {
let zero = (0..dual_shape.len())
.map(|_| Complex::new(Q.zero(), Q.zero()))
.collect();
let dual = Dualizer::new(HyperDual::from_values(dual_shape, zero), zero_components);
let r = self
.eval_complex
.clone()
.set_coeff(&self.rational_constants)
.vectorize(&dual)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Could not dualize evaluator: {}", e))
})?;
self.rational_constants = r.get_constants().to_vec();
self.eval_complex = r.map_coeff(&|c| Complex::new(c.re.to_f64(), c.im.to_f64()));
self.eval_real = None;
#[cfg(feature = "native_code_generation")]
{
self.jit_real = None;
self.jit_complex = None;
}
self.eval_arb_prec = None;
self.eval_arb_prec_complex = None;
Ok(())
}
#[pyo3(signature = (real_params, sqrt_real = false, log_real = false, powf_real = false, real_if_args_real = false, verbose = false))]
fn set_real_params(
&mut self,
real_params: Vec<usize>,
sqrt_real: bool,
log_real: bool,
powf_real: bool,
real_if_args_real: bool,
verbose: bool,
) -> PyResult<()> {
#[cfg(feature = "native_code_generation")]
{
self.jit_complex = None; }
let mut settings = ComplexEvaluatorSettings::new(sqrt_real, log_real, powf_real, verbose);
if real_if_args_real {
settings = settings.real_if_args_real();
}
self.eval_complex
.set_real_params(&real_params, settings)
.map_err(|e| exceptions::PyValueError::new_err(e.to_string()))
}
#[gen_stub(skip)]
#[cfg(feature = "native_code_generation")]
#[pyo3(signature =
(function_name,
filename,
library_name,
number_type,
inline_asm = "default",
optimization_level = 3,
native = true,
compiler_path = None,
compiler_flags = None,
custom_header = None,
cuda_number_of_evaluations = 1,
cuda_block_size = 512
))]
fn compile(
&self,
function_name: &str,
filename: &str,
library_name: &str,
number_type: &str,
inline_asm: &str,
optimization_level: u8,
native: bool,
compiler_path: Option<&str>,
compiler_flags: Option<Vec<String>>,
custom_header: Option<String>,
cuda_number_of_evaluations: usize,
cuda_block_size: usize,
py: Python,
) -> PyResult<Py<PyAny>> {
let mut options = match number_type {
"real" | "complex" => CompileOptions {
optimization_level: optimization_level as usize,
native,
..f64::get_default_compile_options()
},
"real_4x" | "complex_4x" => CompileOptions {
optimization_level: optimization_level as usize,
native,
..<wide::f64x4>::get_default_compile_options()
},
"cuda_real" | "cuda_complex" => CompileOptions {
optimization_level: optimization_level as usize,
..CudaRealf64::get_default_compile_options()
},
_ => {
return Err(exceptions::PyValueError::new_err(format!(
"Invalid number type {} specified.",
number_type,
)));
}
};
if let Some(compiler_path) = compiler_path {
options.compiler = compiler_path.to_string();
}
if let Some(compiler_flags) = compiler_flags {
options.args = compiler_flags;
}
let inline_asm = match inline_asm.to_lowercase().as_str() {
"default" => InlineASM::default(),
"x64" => InlineASM::X64,
"avx2" => InlineASM::AVX2,
"aarch64" => InlineASM::AArch64,
"none" => InlineASM::None,
_ => {
return Err(exceptions::PyValueError::new_err(
"Invalid inline assembly type specified.",
));
}
};
match number_type {
"real" => PythonCompiledRealExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<f64>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load()
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
"complex" => PythonCompiledComplexExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<Complex<f64>>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load()
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
"real_4x" => PythonCompiledSimdRealExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<wide::f64x4>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load()
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
"complex_4x" => PythonCompiledSimdComplexExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<Complex<wide::f64x4>>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load()
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
"cuda_real" => PythonCompiledCudaRealExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<CudaRealf64>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load_with_settings(CudaLoadSettings {
number_of_evaluations: cuda_number_of_evaluations,
block_size: cuda_block_size,
})
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
"cuda_complex" => PythonCompiledCudaComplexExpressionEvaluator {
eval: self
.eval_complex
.export_cpp::<CudaComplexf64>(
filename,
function_name,
ExportSettings {
include_header: true,
inline_asm,
custom_header,
..Default::default()
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Export error: {}", e)))?
.compile(library_name, options)
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Compilation error: {}", e))
})?
.load_with_settings(CudaLoadSettings {
number_of_evaluations: cuda_number_of_evaluations,
block_size: cuda_block_size,
})
.map_err(|e| {
exceptions::PyValueError::new_err(format!("Library loading error: {}", e))
})?,
}
.into_py_any(py),
_ => Err(exceptions::PyValueError::new_err(format!(
"Invalid number type {} specified.",
number_type,
))),
}
}
}
#[cfg(all(feature = "python_stubgen", feature = "native_code_generation"))]
mod compile_stub {
use super::*;
static ONE: fn() -> String = || "1".into();
static THREE: fn() -> String = || "3".into();
static TRUE_ARG: fn() -> String = || "True".into();
static CUDA_BLOCK_DEFAULT: fn() -> String = || "256".into();
static DEFAULT: fn() -> String = || "\"default\"".into();
submit! {
PyMethodsInfo {
struct_id: std::any::TypeId::of::<PythonExpressionEvaluator>,
attrs: &[],
getters: &[],
setters: &[],
file: "python.rs",
line: line!(),
column: column!(),
methods: &[
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['real']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
],
is_overload: true,
r#type: MethodType::Class,
r#return: || PythonCompiledRealExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library using C++ and optionally inline assembly and load it.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code."#,
is_async: false,
deprecated: None,
type_ignored: None,
},
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['complex']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
],
r#type: MethodType::Class,
r#return: || PythonCompiledComplexExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library using C++ and optionally inline assembly and load it.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code."#,
is_async: false,
deprecated: None,
type_ignored: None,
is_overload: true,
},
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['real_4x']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
],
r#type: MethodType::Class,
r#return: || PythonCompiledSimdRealExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library with 4x SIMD using C++ and optionally inline assembly and load it.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code."#,
is_async: false,
deprecated: None,
type_ignored: None,
is_overload: true,
},
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['complex_4x']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
],
r#type: MethodType::Class,
r#return: || PythonCompiledSimdComplexExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library with 4x SIMD using C++ and optionally inline assembly and load it.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code."#,
is_async: false,
deprecated: None,
type_ignored: None,
is_overload: true,
},
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['cuda_real']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "cuda_number_of_evaluations",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(ONE),
type_info: || Option::<usize>::type_input(),
},
ParameterInfo {
name: "cuda_block_size",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(CUDA_BLOCK_DEFAULT),
type_info: || Option::<usize>::type_input(),
},
],
r#type: MethodType::Class,
r#return: || PythonCompiledCudaRealExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library using C++ and optionally inline assembly and load it.
You may have to specify `-code=sm_XY` for your architecture `XY` in the compiler flags to prevent a potentially long
JIT compilation upon the first evaluation.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code.
cuda_number_of_evaluations: int | None
The number of parallel evaluations to perform on the CUDA device. The input to evaluate must
have the length `cuda_number_of_evaluations * arg_len`.
cuda_block_size: int | None
The block size for CUDA kernel launches."#,
is_async: false,
deprecated: None,
type_ignored: None,
is_overload: true,
},
MethodInfo {
name: "compile",
parameters: &[
ParameterInfo {
name: "function_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "filename",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "library_name",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "number_type",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::None,
type_info: || TypeInfo::unqualified("typing.Literal['cuda_complex']"),
},
ParameterInfo {
name: "inline_asm",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(DEFAULT),
type_info: || <&str>::type_input(),
},
ParameterInfo {
name: "optimization_level",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(THREE),
type_info: || Option::<u8>::type_input(),
},
ParameterInfo {
name: "native",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(TRUE_ARG),
type_info: || bool::type_input(),
},
ParameterInfo {
name: "compiler_path",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "compiler_flags",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<Vec<String>>::type_input(),
},
ParameterInfo {
name: "custom_header",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(NONE_ARG),
type_info: || Option::<String>::type_input(),
},
ParameterInfo {
name: "cuda_number_of_evaluations",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(ONE),
type_info: || Option::<usize>::type_input(),
},
ParameterInfo {
name: "cuda_block_size",
kind: ParameterKind::PositionalOrKeyword,
default: ParameterDefault::Expr(CUDA_BLOCK_DEFAULT),
type_info: || Option::<usize>::type_input(),
},
],
r#type: MethodType::Class,
r#return: || PythonCompiledCudaComplexExpressionEvaluator::type_output(),
doc:
r#"Compile the evaluator to a shared library using C++ and optionally inline assembly and load it.
You may have to specify `-code=sm_XY` for your architecture `XY` in the compiler flags to prevent a potentially long
JIT compilation upon the first evaluation.
Parameters
----------
function_name : str
The name of the function to generate and compile.
filename : str
The name of the file to generate.
library_name : str
The name of the shared library to generate.
number_type : Literal['real'] | Literal['complex'] | Literal['real_4x'] | Literal['complex_4x'] | Literal['cuda_real'] | Literal['cuda_complex']
The numeric backend to generate. Use 'real' for double precision or 'complex' for complex double.
For 4x SIMD runs, use 'real_4x' or 'complex_4x'.
For GPU runs with CUDA, use 'cuda_real' or 'cuda_complex'.
inline_asm : str
The inline ASM option can be set to 'default', 'x64', 'avx2', 'aarch64' or 'none'.
optimization_level : int
The compiler optimization level. This can be set to 0, 1, 2 or 3.
native: bool
If `True`, compile for the native architecture. This may produce faster code, but is less portable.
compiler_path : str | None
The custom path to the compiler executable.
compiler_flags : Sequence[str] | None
The custom flags to pass to the compiler.
custom_header : str | None
The custom header to include in the generated code.
cuda_number_of_evaluations: int | None
The number of parallel evaluations to perform on the CUDA device. The input to evaluate must
have the length `cuda_number_of_evaluations * arg_len`.
cuda_block_size: int | None
The block size for CUDA kernel launches."#,
is_async: false,
deprecated: None,
type_ignored: None,
is_overload: true,
}
],
}
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledRealEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledRealExpressionEvaluator {
pub eval: CompiledRealEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledRealExpressionEvaluator {
#[classmethod]
fn load(_cls: &Bound<'_, PyType>, filename: &str, function_name: &str) -> PyResult<Self> {
Ok(Self {
eval: CompiledRealEvaluator::load(filename, function_name)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.float64]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, f64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
self.eval.evaluate(
i.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
);
}
Ok(out.into_pyarray(py))
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledSimdRealEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledSimdRealExpressionEvaluator {
pub eval: CompiledSimdRealEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledSimdRealExpressionEvaluator {
#[classmethod]
fn load(_cls: &Bound<'_, PyType>, filename: &str, function_name: &str) -> PyResult<Self> {
Ok(Self {
eval: CompiledSimdRealEvaluator::load(filename, function_name)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.float64]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, f64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
self.eval
.evaluate_batch(
n_inputs,
arr.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Batch error: {}", e)))?;
Ok(out.into_pyarray(py))
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledCudaRealEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledCudaRealExpressionEvaluator {
pub eval: CompiledCudaRealEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledCudaRealExpressionEvaluator {
#[pyo3(signature =
(filename, function_name, number_of_evaluations, block_size = 512))]
#[classmethod]
fn load(
_cls: &Bound<'_, PyType>,
filename: &str,
function_name: &str,
number_of_evaluations: usize,
block_size: usize,
) -> PyResult<Self> {
Ok(Self {
eval: CompiledCudaRealEvaluator::load_with_settings(
filename,
function_name,
CudaLoadSettings {
number_of_evaluations,
block_size,
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.float64]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, f64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
self.eval
.evaluate(
arr.as_slice().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert input to slice")
})?,
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Evaluation error: {}", e)))?;
Ok(out.into_pyarray(py))
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledCudaComplexEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledCudaComplexExpressionEvaluator {
pub eval: CompiledCudaComplexEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledCudaComplexExpressionEvaluator {
#[pyo3(signature =
(filename, function_name, number_of_evaluations, block_size = 512))]
#[classmethod]
fn load(
_cls: &Bound<'_, PyType>,
filename: &str,
function_name: &str,
number_of_evaluations: usize,
block_size: usize,
) -> PyResult<Self> {
Ok(Self {
eval: CompiledCudaComplexEvaluator::load_with_settings(
filename,
function_name,
CudaLoadSettings {
number_of_evaluations,
block_size,
},
)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.complex128]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, Complex64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(arr.as_slice().ok_or_else(
|| exceptions::PyValueError::new_err("Failed to convert input to slice"),
)?)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
};
self.eval
.evaluate(sc, os)
.map_err(|e| exceptions::PyValueError::new_err(format!("Evaluation error: {}", e)))?;
Ok(out.into_pyarray(py))
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledComplexEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledComplexExpressionEvaluator {
pub eval: CompiledComplexEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledComplexExpressionEvaluator {
#[classmethod]
fn load(_cls: &Bound<'_, PyType>, filename: &str, function_name: &str) -> PyResult<Self> {
Ok(Self {
eval: CompiledComplexEvaluator::load(filename, function_name)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.complex128]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, Complex64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
for (i, mut o) in arr.axis_iter(Axis(0)).zip(out.axis_iter_mut(Axis(0))) {
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(i.as_slice().ok_or_else(
|| exceptions::PyValueError::new_err("Failed to convert input to slice"),
)?)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
o.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
};
self.eval.evaluate(sc, os);
}
Ok(out.into_pyarray(py))
}
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pyclass)]
#[pyclass(
from_py_object,
name = "CompiledSimdComplexEvaluator",
module = "symbolica.core"
)]
#[derive(Clone)]
pub struct PythonCompiledSimdComplexExpressionEvaluator {
pub eval: CompiledSimdComplexEvaluator,
}
#[cfg(feature = "native_code_generation")]
#[cfg_attr(feature = "python_stubgen", gen_stub_pymethods)]
#[cfg_attr(not(feature = "python_stubgen"), remove_gen_stub)]
#[pymethods]
impl PythonCompiledSimdComplexExpressionEvaluator {
#[classmethod]
fn load(_cls: &Bound<'_, PyType>, filename: &str, function_name: &str) -> PyResult<Self> {
Ok(Self {
eval: CompiledSimdComplexEvaluator::load(filename, function_name)
.map_err(|e| exceptions::PyValueError::new_err(format!("Load error: {}", e)))?,
})
}
#[gen_stub(override_return_type(
type_repr = "numpy.typing.NDArray[numpy.complex128]",
imports = ("numpy.typing", "numpy")
))]
fn evaluate<'py>(
&mut self,
#[gen_stub(override_type(
type_repr = "numpy.typing.ArrayLike",
imports = ("numpy.typing",),
))]
inputs: PyArrayLikeDyn<'py, Complex64, AllowTypeChange>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let arr =
reshape_evaluator_inputs(CowArray::from(inputs.as_array()), self.eval.get_input_len())?;
let n_inputs = arr.shape()[0];
let mut out = ArrayD::zeros(&[n_inputs, self.eval.get_output_len()][..]);
let sc = unsafe {
std::mem::transmute::<&[Complex64], &[Complex<f64>]>(arr.as_slice().ok_or_else(
|| exceptions::PyValueError::new_err("Failed to convert input to slice"),
)?)
};
let os = unsafe {
std::mem::transmute::<&mut [Complex64], &mut [Complex<f64>]>(
out.as_slice_mut().ok_or_else(|| {
exceptions::PyValueError::new_err("Failed to convert output to slice")
})?,
)
};
self.eval
.evaluate_batch(n_inputs, sc, os)
.map_err(|e| exceptions::PyValueError::new_err(format!("Batch error: {}", e)))?;
Ok(out.into_pyarray(py))
}
}