use std::str::FromStr;
use pyo3::{
exceptions::{PyOSError, PyValueError},
prelude::*,
PyErr,
};
use crate::{
assembly::{depth, index, index_search, ParallelMode},
bounds::Bound as OurBound,
canonize::CanonizeMode,
kernels::KernelMode,
loader::{parse_molfile_str, ParserError},
memoize::MemoizeMode,
};
impl From<ParserError> for PyErr {
fn from(err: ParserError) -> PyErr {
PyOSError::new_err(err.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PyCanonizeMode {
Nauty,
Faulon,
TreeNauty,
TreeFaulon,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PyParallelMode {
None,
DepthOne,
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PyMemoizeMode {
None,
CanonIndex,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PyKernelMode {
None,
Once,
DepthOne,
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PyBound {
Log,
Int,
VecSimple,
VecSmallFrags,
MatchableEdges,
}
impl FromStr for PyCanonizeMode {
type Err = PyErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"nauty" => Ok(PyCanonizeMode::Nauty),
"faulon" => Ok(PyCanonizeMode::Faulon),
"tree-nauty" => Ok(PyCanonizeMode::TreeNauty),
"tree-faulon" => Ok(PyCanonizeMode::TreeFaulon),
_ => Err(PyValueError::new_err(format!(
"Invalid canonization mode \"{s}\", options are: \
[\"nauty\", \"faulon\", \"tree-nauty\", \"tree-faulon\"]"
))),
}
}
}
impl FromStr for PyParallelMode {
type Err = PyErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(PyParallelMode::None),
"depth-one" => Ok(PyParallelMode::DepthOne),
"always" => Ok(PyParallelMode::Always),
_ => Err(PyValueError::new_err(format!(
"Invalid parallelization mode \"{s}\", options are: \
[\"none\", \"depth-one\", \"always\"]"
))),
}
}
}
impl FromStr for PyMemoizeMode {
type Err = PyErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(PyMemoizeMode::None),
"canon-index" => Ok(PyMemoizeMode::CanonIndex),
_ => Err(PyValueError::new_err(format!(
"Invalid memoization mode \"{s}\", options are: \
[\"none\", \"frags-index\", \"canon-index\"]"
))),
}
}
}
impl FromStr for PyKernelMode {
type Err = PyErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(PyKernelMode::None),
"once" => Ok(PyKernelMode::Once),
"depth-one" => Ok(PyKernelMode::DepthOne),
"always" => Ok(PyKernelMode::Always),
_ => Err(PyValueError::new_err(format!(
"Invalid kernelization mode \"{s}\", options are: \
[\"none\", \"once\", \"depth-one\", \"always\"]"
))),
}
}
}
impl FromStr for PyBound {
type Err = PyErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"log" => Ok(PyBound::Log),
"int" => Ok(PyBound::Int),
"vec-simple" => Ok(PyBound::VecSimple),
"vec-small-frags" => Ok(PyBound::VecSmallFrags),
"matchable-edges" => Ok(PyBound::MatchableEdges),
_ => Err(PyValueError::new_err(format!(
"Invalid bound \"{s}\", options are: \
[\"log\", \"int\", \"vec-simple\", \"vec-small-frags\", \
\"matchable-edges\"]"
))),
}
}
}
fn process_bound_strs(bound_strs: Vec<String>) -> PyResult<Vec<PyBound>> {
bound_strs
.iter()
.map(|s| s.parse())
.collect::<Result<_, _>>()
}
fn make_boundlist(pybounds: &[PyBound]) -> Vec<OurBound> {
let mut boundlist = pybounds
.iter()
.flat_map(|b| match b {
PyBound::Log => vec![OurBound::Log],
PyBound::Int => vec![OurBound::Int],
PyBound::VecSimple => vec![OurBound::VecSimple],
PyBound::VecSmallFrags => vec![OurBound::VecSmallFrags],
PyBound::MatchableEdges => vec![OurBound::MatchableEdges],
})
.collect::<Vec<_>>();
boundlist.dedup();
boundlist
}
#[pyfunction(name = "mol_info")]
pub fn _mol_info(mol_block: &str) -> PyResult<String> {
let mol = parse_molfile_str(mol_block)?;
Ok(mol.info())
}
#[pyfunction(name = "depth")]
pub fn _depth(mol_block: &str) -> PyResult<u32> {
let mol = parse_molfile_str(mol_block)?;
Ok(depth(&mol))
}
#[pyfunction(name = "index")]
pub fn _index(mol_block: &str) -> PyResult<u32> {
let mol = parse_molfile_str(mol_block)?;
Ok(index(&mol))
}
#[pyfunction(name = "index_search")]
#[pyo3(signature = (mol_block, timeout=None, canonize_str="tree-nauty", parallel_str="depth-one", memoize_str="canon-index", kernel_str="none", bound_strs=vec!["int".to_string(), "matchable-edges".to_string()]), text_signature = "(mol_block, canonize_str=\"tree-nauty\", parallel_str=\"depth-one\", memoize_str=\"canon-index\", kernel_str=\"none\", bound_strs=[\"int\", \"matchable-edges\"]))")]
pub fn _index_search(
mol_block: &str,
timeout: Option<u64>,
canonize_str: &str,
parallel_str: &str,
memoize_str: &str,
kernel_str: &str,
bound_strs: Vec<String>,
) -> PyResult<(u32, u32, Option<usize>)> {
let mol = parse_molfile_str(mol_block)?;
let canonize_mode = match PyCanonizeMode::from_str(canonize_str) {
Ok(PyCanonizeMode::Nauty) => CanonizeMode::Nauty,
Ok(PyCanonizeMode::Faulon) => CanonizeMode::Faulon,
Ok(PyCanonizeMode::TreeNauty) => CanonizeMode::TreeNauty,
Ok(PyCanonizeMode::TreeFaulon) => CanonizeMode::TreeFaulon,
Err(e) => return Err(e),
};
let parallel_mode = match PyParallelMode::from_str(parallel_str) {
Ok(PyParallelMode::None) => ParallelMode::None,
Ok(PyParallelMode::DepthOne) => ParallelMode::DepthOne,
Ok(PyParallelMode::Always) => ParallelMode::Always,
Err(e) => return Err(e),
};
let memoize_mode = match PyMemoizeMode::from_str(memoize_str) {
Ok(PyMemoizeMode::None) => MemoizeMode::None,
Ok(PyMemoizeMode::CanonIndex) => MemoizeMode::CanonIndex,
Err(e) => return Err(e),
};
let kernel_mode = match PyKernelMode::from_str(kernel_str) {
Ok(PyKernelMode::None) => KernelMode::None,
Ok(PyKernelMode::Once) => KernelMode::Once,
Ok(PyKernelMode::DepthOne) => KernelMode::DepthOne,
Ok(PyKernelMode::Always) => KernelMode::Always,
Err(e) => return Err(e),
};
let pybounds = process_bound_strs(bound_strs)?;
let boundlist = make_boundlist(&pybounds);
Ok(index_search(
&mol,
timeout,
canonize_mode,
parallel_mode,
memoize_mode,
kernel_mode,
&boundlist,
))
}
#[pymodule(name = "assembly_theory")]
fn _assembly_theory(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_mol_info, m)?)?;
m.add_function(wrap_pyfunction!(_depth, m)?)?;
m.add_function(wrap_pyfunction!(_index, m)?)?;
m.add_function(wrap_pyfunction!(_index_search, m)?)?;
Ok(())
}