use scirs2_core::ndarray::{Array2, ArrayView2};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use sklears_core::{
error::{Result as SklResult, SklearsError},
traits::{Estimator, Fit, Transform},
types::Float,
};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
static PLUGIN_REGISTRY: once_cell::sync::Lazy<RwLock<PluginRegistry>> =
once_cell::sync::Lazy::new(|| RwLock::new(PluginRegistry::new()));
pub trait ManifoldPlugin: Send + Sync + Debug {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn description(&self) -> &str;
fn author(&self) -> &str;
fn create_default(&self) -> Box<dyn CustomManifoldLearner>;
fn create_with_params(
&self,
params: &PluginParameters,
) -> SklResult<Box<dyn CustomManifoldLearner>>;
fn default_parameters(&self) -> PluginParameters;
fn validate_parameters(&self, params: &PluginParameters) -> SklResult<()>;
fn metadata(&self) -> PluginMetadata {
PluginMetadata {
name: self.name().to_string(),
version: self.version().to_string(),
description: self.description().to_string(),
author: self.author().to_string(),
supported_features: self.supported_features(),
parameter_schema: self.parameter_schema(),
}
}
fn supported_features(&self) -> Vec<PluginFeature> {
vec![PluginFeature::DimensionalityReduction]
}
fn parameter_schema(&self) -> Vec<ParameterDefinition>;
}
pub trait CustomManifoldLearner: Send + Sync + Debug {
fn set_parameter(&mut self, name: &str, value: ParameterValue) -> SklResult<()>;
fn get_parameter(&self, name: &str) -> Option<ParameterValue>;
fn get_all_parameters(&self) -> HashMap<String, ParameterValue>;
fn fit(&mut self, x: &ArrayView2<Float>) -> SklResult<()>;
fn transform(&self, x: &ArrayView2<Float>) -> SklResult<Array2<Float>>;
fn fit_transform(&mut self, x: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
self.fit(x)?;
self.transform(x)
}
fn is_fitted(&self) -> bool;
fn get_metadata(&self) -> CustomModelMetadata;
fn clone_learner(&self) -> Box<dyn CustomManifoldLearner>;
}
#[derive(Debug)]
pub struct PluginRegistry {
plugins: HashMap<String, Arc<dyn ManifoldPlugin>>,
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
}
}
pub fn register_plugin(&mut self, plugin: Arc<dyn ManifoldPlugin>) -> SklResult<()> {
let name = plugin.name().to_string();
if self.plugins.contains_key(&name) {
return Err(SklearsError::InvalidInput(format!(
"Plugin '{}' is already registered",
name
)));
}
self.plugins.insert(name, plugin);
Ok(())
}
pub fn unregister_plugin(&mut self, name: &str) -> SklResult<()> {
if self.plugins.remove(name).is_none() {
return Err(SklearsError::InvalidInput(format!(
"Plugin '{}' is not registered",
name
)));
}
Ok(())
}
pub fn get_plugin(&self, name: &str) -> Option<Arc<dyn ManifoldPlugin>> {
self.plugins.get(name).cloned()
}
pub fn list_plugins(&self) -> Vec<String> {
self.plugins.keys().cloned().collect()
}
pub fn get_all_metadata(&self) -> Vec<PluginMetadata> {
self.plugins
.values()
.map(|plugin| plugin.metadata())
.collect()
}
pub fn create_instance(
&self,
name: &str,
params: Option<&PluginParameters>,
) -> SklResult<Box<dyn CustomManifoldLearner>> {
let plugin = self
.get_plugin(name)
.ok_or_else(|| SklearsError::InvalidInput(format!("Plugin '{}' not found", name)))?;
match params {
Some(params) => plugin.create_with_params(params),
None => Ok(plugin.create_default()),
}
}
}
impl PluginRegistry {
pub fn global() -> &'static RwLock<PluginRegistry> {
&PLUGIN_REGISTRY
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PluginParameters {
parameters: HashMap<String, ParameterValue>,
}
impl PluginParameters {
pub fn new() -> Self {
Self {
parameters: HashMap::new(),
}
}
pub fn set<T: Into<ParameterValue>>(&mut self, name: &str, value: T) -> &mut Self {
self.parameters.insert(name.to_string(), value.into());
self
}
pub fn get(&self, name: &str) -> Option<&ParameterValue> {
self.parameters.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.parameters.contains_key(name)
}
pub fn all(&self) -> &HashMap<String, ParameterValue> {
&self.parameters
}
pub fn merge(&mut self, other: &PluginParameters) {
for (key, value) in &other.parameters {
self.parameters.insert(key.clone(), value.clone());
}
}
}
impl Default for PluginParameters {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ParameterValue {
Int(i64),
Float(f64),
String(String),
Bool(bool),
IntArray(Vec<i64>),
FloatArray(Vec<f64>),
StringArray(Vec<String>),
}
impl From<i64> for ParameterValue {
fn from(value: i64) -> Self {
ParameterValue::Int(value)
}
}
impl From<i32> for ParameterValue {
fn from(value: i32) -> Self {
ParameterValue::Int(value as i64)
}
}
impl From<usize> for ParameterValue {
fn from(value: usize) -> Self {
ParameterValue::Int(value as i64)
}
}
impl From<f64> for ParameterValue {
fn from(value: f64) -> Self {
ParameterValue::Float(value)
}
}
impl From<f32> for ParameterValue {
fn from(value: f32) -> Self {
ParameterValue::Float(value as f64)
}
}
impl From<String> for ParameterValue {
fn from(value: String) -> Self {
ParameterValue::String(value)
}
}
impl From<&str> for ParameterValue {
fn from(value: &str) -> Self {
ParameterValue::String(value.to_string())
}
}
impl From<bool> for ParameterValue {
fn from(value: bool) -> Self {
ParameterValue::Bool(value)
}
}
impl From<Vec<i64>> for ParameterValue {
fn from(value: Vec<i64>) -> Self {
ParameterValue::IntArray(value)
}
}
impl From<Vec<f64>> for ParameterValue {
fn from(value: Vec<f64>) -> Self {
ParameterValue::FloatArray(value)
}
}
impl From<Vec<String>> for ParameterValue {
fn from(value: Vec<String>) -> Self {
ParameterValue::StringArray(value)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PluginFeature {
DimensionalityReduction,
Clustering,
Classification,
Regression,
Visualization,
OutOfSample,
IncrementalLearning,
Parallelization,
GPU,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ParameterDefinition {
pub name: String,
pub param_type: ParameterType,
pub description: String,
pub default_value: Option<ParameterValue>,
pub required: bool,
pub constraints: Option<ParameterConstraints>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ParameterType {
Int,
Float,
String,
Bool,
IntArray,
FloatArray,
StringArray,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ParameterConstraints {
pub min_value: Option<f64>,
pub max_value: Option<f64>,
pub allowed_values: Option<Vec<String>>,
pub min_length: Option<usize>,
pub max_length: Option<usize>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub supported_features: Vec<PluginFeature>,
pub parameter_schema: Vec<ParameterDefinition>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CustomModelMetadata {
pub plugin_name: String,
pub plugin_version: String,
pub is_fitted: bool,
pub n_samples: Option<usize>,
pub n_features: Option<usize>,
pub n_components: Option<usize>,
pub training_time: Option<f64>,
pub parameters: HashMap<String, ParameterValue>,
}
#[derive(Debug)]
pub struct CustomManifoldWrapper {
learner: Box<dyn CustomManifoldLearner>,
plugin_name: String,
}
impl CustomManifoldWrapper {
pub fn new(plugin_name: &str, params: Option<&PluginParameters>) -> SklResult<Self> {
let registry = PLUGIN_REGISTRY.read().expect("operation should succeed");
let learner = registry.create_instance(plugin_name, params)?;
Ok(Self {
learner,
plugin_name: plugin_name.to_string(),
})
}
pub fn learner(&self) -> &dyn CustomManifoldLearner {
self.learner.as_ref()
}
pub fn learner_mut(&mut self) -> &mut dyn CustomManifoldLearner {
self.learner.as_mut()
}
pub fn plugin_name(&self) -> &str {
&self.plugin_name
}
}
impl Clone for CustomManifoldWrapper {
fn clone(&self) -> Self {
Self {
learner: self.learner.clone_learner(),
plugin_name: self.plugin_name.clone(),
}
}
}
impl Estimator for CustomManifoldWrapper {
type Config = PluginParameters;
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
static EMPTY_CONFIG: once_cell::sync::Lazy<PluginParameters> =
once_cell::sync::Lazy::new(PluginParameters::new);
&EMPTY_CONFIG
}
}
impl Fit<ArrayView2<'_, Float>, ()> for CustomManifoldWrapper {
type Fitted = CustomManifoldWrapper;
fn fit(mut self, x: &ArrayView2<'_, Float>, _y: &()) -> SklResult<Self::Fitted> {
self.learner.fit(x)?;
Ok(self)
}
}
impl Transform<ArrayView2<'_, Float>, Array2<Float>> for CustomManifoldWrapper {
fn transform(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
self.learner.transform(x)
}
}
pub mod utils {
use super::*;
pub fn register_plugin(plugin: Arc<dyn ManifoldPlugin>) -> SklResult<()> {
PLUGIN_REGISTRY
.write()
.expect("operation should succeed")
.register_plugin(plugin)
}
pub fn unregister_plugin(name: &str) -> SklResult<()> {
PLUGIN_REGISTRY
.write()
.expect("operation should succeed")
.unregister_plugin(name)
}
pub fn list_plugins() -> Vec<String> {
PLUGIN_REGISTRY
.read()
.expect("operation should succeed")
.list_plugins()
}
pub fn get_plugin_metadata(name: &str) -> Option<PluginMetadata> {
PLUGIN_REGISTRY
.read()
.expect("operation should succeed")
.get_plugin(name)
.map(|p| p.metadata())
}
pub fn get_all_plugin_metadata() -> Vec<PluginMetadata> {
PLUGIN_REGISTRY
.read()
.expect("operation should succeed")
.get_all_metadata()
}
pub fn create_plugin_instance(
name: &str,
params: Option<&PluginParameters>,
) -> SklResult<CustomManifoldWrapper> {
CustomManifoldWrapper::new(name, params)
}
pub fn validate_parameters(plugin_name: &str, params: &PluginParameters) -> SklResult<()> {
let registry = PLUGIN_REGISTRY.read().expect("operation should succeed");
let plugin = registry.get_plugin(plugin_name).ok_or_else(|| {
SklearsError::InvalidInput(format!("Plugin '{}' not found", plugin_name))
})?;
plugin.validate_parameters(params)
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::{Array2, ArrayView2};
use scirs2_core::random::thread_rng;
#[derive(Debug)]
struct ExamplePlugin;
impl ManifoldPlugin for ExamplePlugin {
fn name(&self) -> &str {
"ExamplePlugin"
}
fn version(&self) -> &str {
"1.0.0"
}
fn description(&self) -> &str {
"An example plugin for testing"
}
fn author(&self) -> &str {
"Test Author"
}
fn create_default(&self) -> Box<dyn CustomManifoldLearner> {
Box::new(ExampleLearner::default())
}
fn create_with_params(
&self,
params: &PluginParameters,
) -> SklResult<Box<dyn CustomManifoldLearner>> {
let mut learner = ExampleLearner::default();
if let Some(ParameterValue::Int(n_components)) = params.get("n_components") {
learner.set_parameter("n_components", ParameterValue::Int(*n_components))?;
}
Ok(Box::new(learner))
}
fn default_parameters(&self) -> PluginParameters {
let mut params = PluginParameters::new();
params.set("n_components", 2i64);
params
}
fn validate_parameters(&self, params: &PluginParameters) -> SklResult<()> {
if let Some(ParameterValue::Int(n_components)) = params.get("n_components") {
if *n_components <= 0 {
return Err(SklearsError::InvalidInput(
"n_components must be positive".to_string(),
));
}
}
Ok(())
}
fn parameter_schema(&self) -> Vec<ParameterDefinition> {
vec![ParameterDefinition {
name: "n_components".to_string(),
param_type: ParameterType::Int,
description: "Number of components".to_string(),
default_value: Some(ParameterValue::Int(2)),
required: false,
constraints: Some(ParameterConstraints {
min_value: Some(1.0),
max_value: None,
allowed_values: None,
min_length: None,
max_length: None,
}),
}]
}
}
#[derive(Debug, Clone)]
struct ExampleLearner {
n_components: usize,
fitted: bool,
embedding: Option<Array2<Float>>,
}
impl Default for ExampleLearner {
fn default() -> Self {
Self {
n_components: 2,
fitted: false,
embedding: None,
}
}
}
impl CustomManifoldLearner for ExampleLearner {
fn set_parameter(&mut self, name: &str, value: ParameterValue) -> SklResult<()> {
match name {
"n_components" => {
if let ParameterValue::Int(val) = value {
self.n_components = val as usize;
Ok(())
} else {
Err(SklearsError::InvalidInput(
"n_components must be an integer".to_string(),
))
}
}
_ => Err(SklearsError::InvalidInput(format!(
"Unknown parameter: {}",
name
))),
}
}
fn get_parameter(&self, name: &str) -> Option<ParameterValue> {
match name {
"n_components" => Some(ParameterValue::Int(self.n_components as i64)),
_ => None,
}
}
fn get_all_parameters(&self) -> HashMap<String, ParameterValue> {
let mut params = HashMap::new();
params.insert(
"n_components".to_string(),
ParameterValue::Int(self.n_components as i64),
);
params
}
fn fit(&mut self, x: &ArrayView2<Float>) -> SklResult<()> {
let (n_samples, _) = x.dim();
let mut rng = thread_rng();
let mut embedding = Array2::zeros((n_samples, self.n_components));
for elem in embedding.iter_mut() {
*elem = rng.random();
}
self.embedding = Some(embedding);
self.fitted = true;
Ok(())
}
fn transform(&self, _x: &ArrayView2<Float>) -> SklResult<Array2<Float>> {
if !self.fitted {
return Err(SklearsError::InvalidInput(
"Model is not fitted".to_string(),
));
}
self.embedding
.clone()
.ok_or_else(|| SklearsError::InvalidInput("No embedding available".to_string()))
}
fn is_fitted(&self) -> bool {
self.fitted
}
fn get_metadata(&self) -> CustomModelMetadata {
CustomModelMetadata {
plugin_name: "ExamplePlugin".to_string(),
plugin_version: "1.0.0".to_string(),
is_fitted: self.fitted,
n_samples: self.embedding.as_ref().map(|e| e.nrows()),
n_features: None,
n_components: Some(self.n_components),
training_time: None,
parameters: self.get_all_parameters(),
}
}
fn clone_learner(&self) -> Box<dyn CustomManifoldLearner> {
Box::new(self.clone())
}
}
#[test]
fn test_plugin_registration() {
let plugin = Arc::new(ExamplePlugin);
let result = utils::register_plugin(plugin);
assert!(result.is_ok());
let plugins = utils::list_plugins();
assert!(plugins.contains(&"ExamplePlugin".to_string()));
utils::unregister_plugin("ExamplePlugin").expect("operation should succeed");
}
#[test]
fn test_plugin_instance_creation() {
let plugin = Arc::new(ExamplePlugin);
utils::register_plugin(plugin).expect("operation should succeed");
let wrapper = utils::create_plugin_instance("ExamplePlugin", None);
assert!(wrapper.is_ok());
utils::unregister_plugin("ExamplePlugin").expect("operation should succeed");
}
#[test]
fn test_parameter_validation() {
let plugin = Arc::new(ExamplePlugin);
utils::register_plugin(plugin).expect("operation should succeed");
let mut params = PluginParameters::new();
params.set("n_components", 5i64);
let result = utils::validate_parameters("ExamplePlugin", ¶ms);
assert!(result.is_ok());
params.set("n_components", -1i64);
let result = utils::validate_parameters("ExamplePlugin", ¶ms);
assert!(result.is_err());
utils::unregister_plugin("ExamplePlugin").expect("operation should succeed");
}
}