use std::collections::HashMap;
use ganesh::python::IntoPySummary;
use laddu_core::{validate_free_parameter_len, LadduError};
use laddu_extensions::{
likelihood::{LikelihoodTerm, StochasticNLL},
LikelihoodExpression, LikelihoodScalar, NLL,
};
use numpy::{PyArray1, PyArray2, PyArray3};
use pyo3::{
exceptions::{PyTypeError, PyValueError},
prelude::*,
types::{PyAny, PyList},
IntoPyObjectExt,
};
use crate::{
amplitudes::{PyCompiledExpression, PyEvaluator, PyExpression, PyParameterMap},
data::PyDataset,
extensions::{
install_laddu_with_threads,
optimize::{mcmc_from_python, minimize_from_python},
},
};
#[cfg_attr(coverage_nightly, coverage(off))]
fn extract_subset_names(subset: Option<Bound<'_, PyAny>>) -> PyResult<Option<Vec<String>>> {
let Some(subset) = subset else {
return Ok(None);
};
if let Ok(string_arg) = subset.extract::<String>() {
Ok(Some(vec![string_arg]))
} else if let Ok(list_arg) = subset.extract::<Vec<String>>() {
Ok(Some(list_arg))
} else {
Err(PyTypeError::new_err(
"subset must be either a string or a list of strings",
))
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn extract_subsets_arg(
subsets: Option<Bound<'_, PyAny>>,
) -> PyResult<Option<Vec<Option<Vec<String>>>>> {
let Some(subsets) = subsets else {
return Ok(None);
};
subsets
.extract::<Vec<Option<Vec<String>>>>()
.map(Some)
.map_err(|_| {
PyTypeError::new_err(
"subsets must be a list whose items are either None or lists of strings",
)
})
}
#[pyclass(name = "LikelihoodExpression", module = "laddu", from_py_object)]
#[derive(Clone)]
pub struct PyLikelihoodExpression(pub LikelihoodExpression);
#[cfg_attr(coverage_nightly, coverage(off))]
#[pyfunction(name = "likelihood_sum")]
pub fn py_likelihood_sum(terms: Vec<Bound<'_, PyAny>>) -> PyResult<PyLikelihoodExpression> {
if terms.is_empty() {
return Ok(PyLikelihoodExpression(LikelihoodExpression::zero()));
}
if terms.len() == 1 {
let term = &terms[0];
if let Ok(expression) = term.extract::<PyLikelihoodExpression>() {
return Ok(expression);
}
return Err(PyTypeError::new_err("Item is not a PyLikelihoodExpression"));
}
let mut iter = terms.iter();
let Some(first_term) = iter.next() else {
return Ok(PyLikelihoodExpression(LikelihoodExpression::zero()));
};
let PyLikelihoodExpression(mut summation) = first_term
.extract::<PyLikelihoodExpression>()
.map_err(|_| PyTypeError::new_err("Elements must be PyLikelihoodExpression"))?;
for term in iter {
let PyLikelihoodExpression(expr) = term
.extract::<PyLikelihoodExpression>()
.map_err(|_| PyTypeError::new_err("Elements must be PyLikelihoodExpression"))?;
summation = summation + expr;
}
Ok(PyLikelihoodExpression(summation))
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[pyfunction(name = "likelihood_product")]
pub fn py_likelihood_product(terms: Vec<Bound<'_, PyAny>>) -> PyResult<PyLikelihoodExpression> {
if terms.is_empty() {
return Ok(PyLikelihoodExpression(LikelihoodExpression::one()));
}
if terms.len() == 1 {
let term = &terms[0];
if let Ok(expression) = term.extract::<PyLikelihoodExpression>() {
return Ok(expression);
}
return Err(PyTypeError::new_err("Item is not a PyLikelihoodExpression"));
}
let mut iter = terms.iter();
let Some(first_term) = iter.next() else {
return Ok(PyLikelihoodExpression(LikelihoodExpression::one()));
};
let PyLikelihoodExpression(mut product) = first_term
.extract::<PyLikelihoodExpression>()
.map_err(|_| PyTypeError::new_err("Elements must be PyLikelihoodExpression"))?;
for term in iter {
let PyLikelihoodExpression(expr) = term
.extract::<PyLikelihoodExpression>()
.map_err(|_| PyTypeError::new_err("Elements must be PyLikelihoodExpression"))?;
product = product * expr;
}
Ok(PyLikelihoodExpression(product))
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[pyfunction(name = "LikelihoodZero")]
pub fn py_likelihood_zero() -> PyLikelihoodExpression {
PyLikelihoodExpression(LikelihoodExpression::zero())
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[pyfunction(name = "LikelihoodOne")]
pub fn py_likelihood_one() -> PyLikelihoodExpression {
PyLikelihoodExpression(LikelihoodExpression::one())
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[pymethods]
impl PyLikelihoodExpression {
#[getter]
fn parameters(&self) -> PyParameterMap {
PyParameterMap(self.0.parameters())
}
fn fix_parameter(&self, name: &str, value: f64) -> PyResult<()> {
Ok(self.0.fix_parameter(name, value)?)
}
fn free_parameter(&self, name: &str) -> PyResult<()> {
Ok(self.0.free_parameter(name)?)
}
fn rename_parameter(&self, old: &str, new: &str) -> PyResult<()> {
Ok(self.0.rename_parameter(old, new)?)
}
fn rename_parameters(&self, mapping: HashMap<String, String>) -> PyResult<()> {
Ok(self.0.rename_parameters(&mapping)?)
}
fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult<PyLikelihoodExpression> {
if let Ok(other_expr) = other.extract::<PyLikelihoodExpression>() {
Ok(PyLikelihoodExpression(
self.0.clone() + other_expr.0.clone(),
))
} else if let Ok(int) = other.extract::<usize>() {
if int == 0 {
Ok(PyLikelihoodExpression(self.0.clone()))
} else {
Err(PyTypeError::new_err(
"Addition with an integer for this type is only defined for 0",
))
}
} else {
Err(PyTypeError::new_err("Unsupported operand type for +"))
}
}
fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult<PyLikelihoodExpression> {
if let Ok(other_expr) = other.extract::<PyLikelihoodExpression>() {
Ok(PyLikelihoodExpression(
other_expr.0.clone() + self.0.clone(),
))
} else if let Ok(int) = other.extract::<usize>() {
if int == 0 {
Ok(PyLikelihoodExpression(self.0.clone()))
} else {
Err(PyTypeError::new_err(
"Addition with an integer for this type is only defined for 0",
))
}
} else {
Err(PyTypeError::new_err("Unsupported operand type for +"))
}
}
fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult<PyLikelihoodExpression> {
if let Ok(other_expr) = other.extract::<PyLikelihoodExpression>() {
Ok(PyLikelihoodExpression(
self.0.clone() * other_expr.0.clone(),
))
} else {
Err(PyTypeError::new_err("Unsupported operand type for *"))
}
}
fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult<PyLikelihoodExpression> {
if let Ok(other_expr) = other.extract::<PyLikelihoodExpression>() {
Ok(PyLikelihoodExpression(
other_expr.0.clone() * self.0.clone(),
))
} else {
Err(PyTypeError::new_err("Unsupported operand type for *"))
}
}
fn __str__(&self) -> String {
format!("{}", self.0)
}
fn __repr__(&self) -> String {
format!("{:?}", self.0)
}
#[getter]
fn n_free(&self) -> usize {
self.0.n_free()
}
#[getter]
fn n_fixed(&self) -> usize {
self.0.n_fixed()
}
#[getter]
fn n_parameters(&self) -> usize {
self.0.n_parameters()
}
#[pyo3(signature = (parameters, *, threads=None))]
fn evaluate(&self, parameters: Vec<f64>, threads: Option<usize>) -> PyResult<f64> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
install_laddu_with_threads(threads, || self.0.evaluate(¶meters)).map_err(PyErr::from)
}
#[pyo3(signature = (parameters, *, threads=None))]
fn evaluate_gradient<'py>(
&self,
py: Python<'py>,
parameters: Vec<f64>,
threads: Option<usize>,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
let gradient =
install_laddu_with_threads(threads, || self.0.evaluate_gradient(¶meters))?;
Ok(PyArray1::from_slice(py, gradient.as_slice()))
}
#[cfg_attr(doctest, doc = "```ignore")]
#[cfg_attr(doctest, doc = "```")]
#[pyo3(signature = (p0, *, method="lbfgsb".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn minimize<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
minimize_from_python(
&self.0,
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
#[pyo3(signature = (p0, *, method="aies".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn mcmc<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
mcmc_from_python(
&self.0,
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
}
#[pyclass(name = "NLL", module = "laddu", from_py_object)]
#[derive(Clone)]
pub struct PyNLL(pub Box<NLL>);
#[cfg_attr(coverage_nightly, coverage(off))]
#[pymethods]
impl PyNLL {
#[new]
#[pyo3(signature = (expression, ds_data, ds_accmc, *, n_mc=None))]
fn new(
expression: &PyExpression,
ds_data: &PyDataset,
ds_accmc: &PyDataset,
n_mc: Option<f64>,
) -> PyResult<Self> {
Ok(Self(NLL::new(
&expression.0,
&ds_data.0,
&ds_accmc.0,
n_mc,
)?))
}
#[getter]
fn data(&self) -> PyDataset {
PyDataset(self.0.data_evaluator.dataset.clone())
}
#[getter]
fn accmc(&self) -> PyDataset {
PyDataset(self.0.accmc_evaluator.dataset.clone())
}
#[getter]
fn data_evaluator(&self) -> PyEvaluator {
PyEvaluator(self.0.data_evaluator.clone())
}
#[getter]
fn accmc_evaluator(&self) -> PyEvaluator {
PyEvaluator(self.0.accmc_evaluator.clone())
}
#[getter]
fn expression(&self) -> PyExpression {
PyExpression(self.0.expression())
}
#[getter]
fn compiled_expression(&self) -> PyCompiledExpression {
PyCompiledExpression(self.0.compiled_expression())
}
#[pyo3(signature = (batch_size, *, seed=None))]
fn to_stochastic(&self, batch_size: usize, seed: Option<usize>) -> PyResult<PyStochasticNLL> {
Ok(PyStochasticNLL(self.0.to_stochastic(batch_size, seed)?))
}
fn to_expression(&self) -> PyResult<PyLikelihoodExpression> {
Ok(PyLikelihoodExpression(self.0.clone().into_expression()?))
}
#[getter]
fn parameters(&self) -> PyParameterMap {
PyParameterMap(self.0.parameters())
}
#[getter]
fn n_free(&self) -> usize {
self.0.n_free()
}
#[getter]
fn n_fixed(&self) -> usize {
self.0.n_fixed()
}
#[getter]
fn n_parameters(&self) -> usize {
self.0.n_parameters()
}
fn fix_parameter(&self, name: &str, value: f64) -> PyResult<()> {
Ok(self.0.fix_parameter(name, value)?)
}
fn free_parameter(&self, name: &str) -> PyResult<()> {
Ok(self.0.free_parameter(name)?)
}
fn rename_parameter(&self, old: &str, new: &str) -> PyResult<()> {
Ok(self.0.rename_parameter(old, new)?)
}
fn rename_parameters(&self, mapping: HashMap<String, String>) -> PyResult<()> {
Ok(self.0.rename_parameters(&mapping)?)
}
#[pyo3(signature = (arg, *, strict=true))]
fn activate(&self, arg: &Bound<'_, PyAny>, strict: bool) -> PyResult<()> {
if let Ok(string_arg) = arg.extract::<String>() {
if strict {
self.0.activate_strict(&string_arg)?;
} else {
self.0.activate(&string_arg);
}
} else if let Ok(list_arg) = arg.cast::<PyList>() {
let vec: Vec<String> = list_arg.extract()?;
if strict {
self.0.activate_many_strict(&vec)?;
} else {
self.0.activate_many(&vec);
}
} else {
return Err(PyTypeError::new_err(
"Argument must be either a string or a list of strings",
));
}
Ok(())
}
fn activate_all(&self) {
self.0.activate_all();
}
#[pyo3(signature = (arg, *, strict=true))]
fn deactivate(&self, arg: &Bound<'_, PyAny>, strict: bool) -> PyResult<()> {
if let Ok(string_arg) = arg.extract::<String>() {
if strict {
self.0.deactivate_strict(&string_arg)?;
} else {
self.0.deactivate(&string_arg);
}
} else if let Ok(list_arg) = arg.cast::<PyList>() {
let vec: Vec<String> = list_arg.extract()?;
if strict {
self.0.deactivate_many_strict(&vec)?;
} else {
self.0.deactivate_many(&vec);
}
} else {
return Err(PyTypeError::new_err(
"Argument must be either a string or a list of strings",
));
}
Ok(())
}
fn deactivate_all(&self) {
self.0.deactivate_all();
}
#[pyo3(signature = (arg, *, strict=true))]
fn isolate(&self, arg: &Bound<'_, PyAny>, strict: bool) -> PyResult<()> {
if let Ok(string_arg) = arg.extract::<String>() {
if strict {
self.0.isolate_strict(&string_arg)?;
} else {
self.0.isolate(&string_arg);
}
} else if let Ok(list_arg) = arg.cast::<PyList>() {
let vec: Vec<String> = list_arg.extract()?;
if strict {
self.0.isolate_many_strict(&vec)?;
} else {
self.0.isolate_many(&vec);
}
} else {
return Err(PyTypeError::new_err(
"Argument must be either a string or a list of strings",
));
}
Ok(())
}
#[pyo3(signature = (parameters, *, threads=None))]
fn evaluate(&self, parameters: Vec<f64>, threads: Option<usize>) -> PyResult<f64> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
install_laddu_with_threads(threads, || {
LikelihoodTerm::evaluate(self.0.as_ref(), ¶meters)
})
.map_err(PyErr::from)
}
#[pyo3(signature = (parameters, *, threads=None))]
fn evaluate_gradient<'py>(
&self,
py: Python<'py>,
parameters: Vec<f64>,
threads: Option<usize>,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
let gradient = install_laddu_with_threads(threads, || {
LikelihoodTerm::evaluate_gradient(self.0.as_ref(), ¶meters)
})?;
Ok(PyArray1::from_slice(py, gradient.as_slice()))
}
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (
parameters,
*,
subset = None,
subsets = None,
strict = false,
mc_evaluator = None,
threads = None
))]
fn project_weights<'py>(
&self,
py: Python<'py>,
parameters: Vec<f64>,
subset: Option<Bound<'_, PyAny>>,
subsets: Option<Bound<'_, PyAny>>,
strict: bool,
mc_evaluator: Option<PyEvaluator>,
threads: Option<usize>,
) -> PyResult<Bound<'py, PyAny>> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
if subset.is_some() && subsets.is_some() {
return Err(PyValueError::new_err(
"subset and subsets are mutually exclusive",
));
}
let subset = extract_subset_names(subset)?;
let subsets = extract_subsets_arg(subsets)?;
let mc_evaluator = mc_evaluator.map(|pyeval| pyeval.0.clone());
match (subset, subsets) {
(Some(names), None) => {
let projection = install_laddu_with_threads(threads, || {
if strict {
self.0.project_weights_subset_strict(
¶meters,
&names,
mc_evaluator.clone(),
)
} else {
self.0
.project_weights_subset(¶meters, &names, mc_evaluator.clone())
}
})?;
Ok(PyArray1::from_slice(py, projection.as_slice()).into_any())
}
(None, Some(subsets)) => {
let projection = install_laddu_with_threads(threads, || {
let mut rows = Vec::with_capacity(subsets.len());
for subset in &subsets {
let weights = match subset {
Some(names) => {
if strict {
self.0.project_weights_subset_strict(
¶meters,
names,
mc_evaluator.clone(),
)?
} else {
self.0.project_weights_subset(
¶meters,
names,
mc_evaluator.clone(),
)?
}
}
None => self.0.project_weights(¶meters, mc_evaluator.clone())?,
};
rows.push(weights);
}
Ok::<_, LadduError>(rows)
})?;
Ok(PyArray2::from_vec2(py, &projection)
.map_err(LadduError::NumpyError)?
.into_any())
}
(None, None) => {
let projection = install_laddu_with_threads(threads, || {
self.0.project_weights(¶meters, mc_evaluator.clone())
})?;
Ok(PyArray1::from_slice(py, projection.as_slice()).into_any())
}
(Some(_), Some(_)) => unreachable!("checked above"),
}
}
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (
parameters,
*,
subset = None,
subsets = None,
strict = false,
mc_evaluator = None,
threads = None
))]
fn project_weights_and_gradients<'py>(
&self,
py: Python<'py>,
parameters: Vec<f64>,
subset: Option<Bound<'_, PyAny>>,
subsets: Option<Bound<'_, PyAny>>,
strict: bool,
mc_evaluator: Option<PyEvaluator>,
threads: Option<usize>,
) -> PyResult<Bound<'py, PyAny>> {
validate_free_parameter_len(parameters.len(), self.0.n_free())?;
if subset.is_some() && subsets.is_some() {
return Err(PyValueError::new_err(
"subset and subsets are mutually exclusive",
));
}
let subset = extract_subset_names(subset)?;
let subsets = extract_subsets_arg(subsets)?;
let mc_evaluator = mc_evaluator.map(|pyeval| pyeval.0.clone());
match (subset, subsets) {
(Some(names), None) => {
let (weights, gradients) = install_laddu_with_threads(threads, || {
if strict {
self.0.project_weights_and_gradients_subset_strict(
¶meters,
&names,
mc_evaluator.clone(),
)
} else {
self.0.project_weights_and_gradients_subset(
¶meters,
&names,
mc_evaluator.clone(),
)
}
})?;
let gradients = gradients
.iter()
.map(|gradient| gradient.as_slice().to_vec())
.collect::<Vec<_>>();
(
PyArray1::from_slice(py, weights.as_slice()),
PyArray2::from_vec2(py, &gradients).map_err(LadduError::NumpyError)?,
)
.into_bound_py_any(py)
}
(None, Some(subsets)) => {
let (weights, gradients) = install_laddu_with_threads(threads, || {
let mut weight_rows = Vec::with_capacity(subsets.len());
let mut gradient_rows = Vec::with_capacity(subsets.len());
for subset in &subsets {
let (subset_weights, subset_gradients) = match subset {
Some(names) => {
if strict {
self.0.project_weights_and_gradients_subset_strict(
¶meters,
names,
mc_evaluator.clone(),
)?
} else {
self.0.project_weights_and_gradients_subset(
¶meters,
names,
mc_evaluator.clone(),
)?
}
}
None => self
.0
.project_weights_and_gradients(¶meters, mc_evaluator.clone())?,
};
weight_rows.push(subset_weights);
gradient_rows.push(
subset_gradients
.iter()
.map(|gradient| gradient.as_slice().to_vec())
.collect::<Vec<_>>(),
);
}
Ok::<_, LadduError>((weight_rows, gradient_rows))
})?;
(
PyArray2::from_vec2(py, &weights).map_err(LadduError::NumpyError)?,
PyArray3::from_vec3(py, &gradients).map_err(LadduError::NumpyError)?,
)
.into_bound_py_any(py)
}
(None, None) => {
let (weights, gradients) = install_laddu_with_threads(threads, || {
self.0
.project_weights_and_gradients(¶meters, mc_evaluator.clone())
})?;
let gradients = gradients
.iter()
.map(|gradient| gradient.as_slice().to_vec())
.collect::<Vec<_>>();
(
PyArray1::from_slice(py, weights.as_slice()),
PyArray2::from_vec2(py, &gradients).map_err(LadduError::NumpyError)?,
)
.into_bound_py_any(py)
}
(Some(_), Some(_)) => unreachable!("checked above"),
}
}
#[pyo3(signature = (p0, *, method="lbfgsb".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn minimize<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
minimize_from_python(
self.0.as_ref(),
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
#[pyo3(signature = (p0, *, method="aies".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn mcmc<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
mcmc_from_python(
self.0.as_ref(),
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
}
#[pyclass(name = "StochasticNLL", module = "laddu", skip_from_py_object)]
#[derive(Clone)]
pub struct PyStochasticNLL(pub StochasticNLL);
#[cfg_attr(coverage_nightly, coverage(off))]
#[pymethods]
impl PyStochasticNLL {
#[getter]
fn nll(&self) -> PyNLL {
PyNLL(Box::new(self.0.nll.clone()))
}
#[getter]
fn expression(&self) -> PyExpression {
PyExpression(self.0.expression())
}
#[getter]
fn compiled_expression(&self) -> PyCompiledExpression {
PyCompiledExpression(self.0.compiled_expression())
}
#[pyo3(signature = (p0, *, method="lbfgsb".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn minimize<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
minimize_from_python(
&self.0,
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
#[pyo3(signature = (p0, *, method="aies".to_string(), config=None, options=None, observers=None, terminators=None, threads=0))]
#[allow(clippy::too_many_arguments)]
fn mcmc<'py>(
&self,
py: Python<'py>,
p0: Bound<'_, PyAny>,
method: String,
config: Option<Bound<'_, PyAny>>,
options: Option<Bound<'_, PyAny>>,
observers: Option<Bound<'_, PyAny>>,
terminators: Option<Bound<'_, PyAny>>,
threads: usize,
) -> PyResult<Bound<'py, PyAny>> {
let parameter_names = self.0.parameters().free().names();
mcmc_from_python(
&self.0,
&p0,
self.0.n_free(),
¶meter_names,
method,
config.as_ref(),
options.as_ref(),
observers,
terminators,
threads,
)?
.to_py_class(py)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[pyfunction(name = "LikelihoodScalar")]
pub fn py_likelihood_scalar(name: String) -> PyResult<PyLikelihoodExpression> {
Ok(PyLikelihoodExpression(LikelihoodScalar::new(name)?))
}