use super::algorithms::EnsembleClusterer;
use super::core::*;
use crate::error::{ClusteringError, Result};
use crate::metrics::silhouette_score;
use scirs2_core::ndarray::ArrayStatCompat;
use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView1, ArrayView2, Axis};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::random::prelude::*;
use scirs2_core::random::{Distribution, WeightedIndex};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct AdvancedEnsembleConfig {
pub meta_learning: MetaLearningConfig,
pub bayesian_averaging: BayesianAveragingConfig,
pub genetic_optimization: GeneticOptimizationConfig,
pub boostingconfig: BoostingConfig,
pub stackingconfig: StackingConfig,
pub uncertainty_quantification: bool,
}
#[derive(Debug, Clone)]
pub struct MetaLearningConfig {
pub n_meta_features: usize,
pub learning_rate: f64,
pub n_iterations: usize,
pub algorithm: MetaLearningAlgorithm,
pub validation_split: f64,
}
#[derive(Debug, Clone)]
pub enum MetaLearningAlgorithm {
NeuralNetwork { hidden_layers: Vec<usize> },
RandomForest { n_trees: usize, max_depth: usize },
GradientBoosting {
n_estimators: usize,
max_depth: usize,
},
Linear { regularization: f64 },
}
#[derive(Debug, Clone)]
pub struct BayesianAveragingConfig {
pub prior_alpha: f64,
pub prior_beta: f64,
pub n_samples: usize,
pub burn_in: usize,
pub update_method: PosteriorUpdateMethod,
pub adaptive_sampling: bool,
}
#[derive(Debug, Clone)]
pub enum PosteriorUpdateMethod {
MetropolisHastings,
Gibbs,
VariationalInference,
HamiltonianMC,
}
#[derive(Debug, Clone)]
pub struct GeneticOptimizationConfig {
pub population_size: usize,
pub n_generations: usize,
pub crossover_prob: f64,
pub mutation_prob: f64,
pub selection_method: SelectionMethod,
pub elite_percentage: f64,
pub fitness_function: FitnessFunction,
}
#[derive(Debug, Clone)]
pub enum SelectionMethod {
Tournament { tournament_size: usize },
RouletteWheel,
RankBased,
Elitist,
}
#[derive(Debug, Clone)]
pub enum FitnessFunction {
Silhouette,
DaviesBouldin,
CalinskiHarabasz,
MultiObjective { weights: Vec<f64> },
Stability,
}
#[derive(Debug, Clone)]
pub struct BoostingConfig {
pub n_rounds: usize,
pub learning_rate: f64,
pub reweighting_strategy: ReweightingStrategy,
pub error_function: ErrorFunction,
pub adaptive_boosting: bool,
}
#[derive(Debug, Clone)]
pub enum ReweightingStrategy {
Exponential,
Linear,
Logistic,
Custom { alpha: f64, beta: f64 },
}
#[derive(Debug, Clone)]
pub enum ErrorFunction {
DisagreementRate,
InverseSilhouette,
WeightedError,
}
#[derive(Debug, Clone)]
pub struct StackingConfig {
pub base_algorithms: Vec<ClusteringAlgorithm>,
pub meta_algorithm: MetaClusteringAlgorithm,
pub cv_folds: usize,
pub blending_ratio: f64,
pub feature_engineering: bool,
}
#[derive(Debug, Clone)]
pub enum MetaClusteringAlgorithm {
Hierarchical { linkage: String },
Spectral { n_clusters: usize },
GraphBased { resolution: f64 },
Consensus { method: String },
}
#[derive(Debug, Clone)]
pub struct MetaLearner {
pub algorithm: MetaLearningAlgorithm,
pub weights: Option<Array1<f64>>,
pub training_history: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct GeneticOptimizer {
config: GeneticOptimizationConfig,
population: Vec<EnsembleConfig>,
fitness_scores: Vec<f64>,
}
impl GeneticOptimizer {
pub fn new(config: GeneticOptimizationConfig) -> Self {
Self {
config,
population: Vec::new(),
fitness_scores: Vec::new(),
}
}
pub fn evolve_ensemble<F>(
&mut self,
base_ensemble: &EnsembleClusterer<F>,
data: ArrayView2<F>,
) -> Result<EnsembleClusterer<F>>
where
F: Float
+ FromPrimitive
+ Debug
+ 'static
+ std::iter::Sum
+ std::fmt::Display
+ Send
+ Sync,
f64: From<F>,
{
self.initialize_population()?;
for _generation in 0..self.config.n_generations {
self.evaluate_population(data)?;
self.selection_and_reproduction()?;
}
let best_config = self.get_best_config()?;
Ok(EnsembleClusterer::new(best_config))
}
fn initialize_population(&mut self) -> Result<()> {
self.population.clear();
for _ in 0..self.config.population_size {
self.population.push(EnsembleConfig::default());
}
Ok(())
}
fn evaluate_population<F>(&mut self, data: ArrayView2<F>) -> Result<()>
where
F: Float
+ FromPrimitive
+ Debug
+ 'static
+ std::iter::Sum
+ std::fmt::Display
+ Send
+ Sync,
f64: From<F>,
{
self.fitness_scores.clear();
for config in &self.population {
let ensemble = EnsembleClusterer::new(config.clone());
let result = ensemble.fit(data)?;
let fitness = match self.config.fitness_function {
FitnessFunction::Silhouette => result.ensemble_quality,
_ => result.ensemble_quality, };
self.fitness_scores.push(fitness);
}
Ok(())
}
fn selection_and_reproduction(&mut self) -> Result<()> {
let mut sorted_indices: Vec<usize> = (0..self.population.len()).collect();
sorted_indices.sort_by(|&a, &b| {
self.fitness_scores[b]
.partial_cmp(&self.fitness_scores[a])
.unwrap_or(Ordering::Equal)
});
let elite_count = (self.population.len() as f64 * self.config.elite_percentage) as usize;
let new_population = sorted_indices[..elite_count]
.iter()
.map(|&i| self.population[i].clone())
.collect();
self.population = new_population;
Ok(())
}
fn get_best_config(&self) -> Result<EnsembleConfig> {
if self.population.is_empty() {
return Ok(EnsembleConfig::default());
}
Ok(self.population[0].clone())
}
}
pub struct AdvancedEnsembleClusterer<F: Float> {
config: AdvancedEnsembleConfig,
base_ensemble: EnsembleClusterer<F>,
meta_learner: Option<MetaLearner>,
bayesian_weights: Option<Array1<f64>>,
genetic_optimizer: Option<GeneticOptimizer>,
_phantom: std::marker::PhantomData<F>,
}
impl<F> AdvancedEnsembleClusterer<F>
where
F: Float + FromPrimitive + Debug + 'static + std::iter::Sum + std::fmt::Display + Send + Sync,
f64: From<F>,
{
pub fn new(config: AdvancedEnsembleConfig, baseconfig: EnsembleConfig) -> Self {
Self {
config,
base_ensemble: EnsembleClusterer::new(baseconfig),
meta_learner: None,
bayesian_weights: None,
genetic_optimizer: None,
_phantom: std::marker::PhantomData,
}
}
pub fn fit_with_meta_learning(&mut self, data: ArrayView2<F>) -> Result<EnsembleResult> {
let base_results = self.base_ensemble.fit(data)?;
let meta_features = self.extract_meta_features(data, &base_results)?;
let weights = self.train_meta_learner(&meta_features, &base_results.individual_results)?;
let enhanced_consensus =
self.weighted_meta_consensus(&base_results.individual_results, &weights, data.nrows())?;
let mut enhanced_result = base_results;
enhanced_result.consensus_labels = enhanced_consensus;
enhanced_result.ensemble_quality = self.calculate_meta_quality(data, &enhanced_result)?;
Ok(enhanced_result)
}
pub fn fit_with_bayesian_averaging(&mut self, data: ArrayView2<F>) -> Result<EnsembleResult> {
let base_results = self.base_ensemble.fit(data)?;
let n_models = base_results.individual_results.len();
let mut weights = Array1::from_elem(n_models, 1.0 / n_models as f64);
for _iteration in 0..self.config.bayesian_averaging.n_samples {
weights = self.mcmc_update_weights(&weights, &base_results, data)?;
}
self.bayesian_weights = Some(weights.clone());
let consensus = self.bayesian_weighted_consensus(
&base_results.individual_results,
&weights,
data.nrows(),
)?;
let mut enhanced_result = base_results;
enhanced_result.consensus_labels = consensus;
Ok(enhanced_result)
}
pub fn fit_with_genetic_optimization(&mut self, data: ArrayView2<F>) -> Result<EnsembleResult> {
let mut optimizer = GeneticOptimizer::new(self.config.genetic_optimization.clone());
let optimized_ensemble = optimizer.evolve_ensemble(&self.base_ensemble, data)?;
optimized_ensemble.fit(data)
}
pub fn fit_with_boosting(&mut self, data: ArrayView2<F>) -> Result<EnsembleResult> {
let mut sample_weights = Array1::from_elem(data.nrows(), 1.0 / data.nrows() as f64);
let mut weak_learners = Vec::new();
let mut learner_weights = Vec::new();
for _round in 0..self.config.boostingconfig.n_rounds {
let weighted_data = self.weighted_sample(data, &sample_weights)?;
let weak_result = self.train_weak_learner(&weighted_data)?;
let error_rate =
self.calculate_clustering_error(data, &weak_result, &sample_weights)?;
if error_rate >= 0.5 {
break; }
let learner_weight =
self.config.boostingconfig.learning_rate * ((1.0 - error_rate) / error_rate).ln();
self.update_sample_weights(&mut sample_weights, &weak_result, learner_weight, data)?;
weak_learners.push(weak_result);
learner_weights.push(learner_weight);
}
self.combine_boosted_learners(&weak_learners, &learner_weights, data.nrows())
}
pub fn fit_with_stacking(&mut self, data: ArrayView2<F>) -> Result<EnsembleResult> {
let cv_folds = self.config.stackingconfig.cv_folds;
let n_samples = data.nrows();
let fold_size = n_samples / cv_folds;
let mut base_predictions =
Array2::zeros((n_samples, self.config.stackingconfig.base_algorithms.len()));
for fold in 0..cv_folds {
let start_idx = fold * fold_size;
let end_idx = if fold == cv_folds - 1 {
n_samples
} else {
(fold + 1) * fold_size
};
let train_indices: Vec<usize> = (0..start_idx).chain(end_idx..n_samples).collect();
let test_indices: Vec<usize> = (start_idx..end_idx).collect();
let train_data = data.select(Axis(0), &train_indices);
let test_data = data.select(Axis(0), &test_indices);
let base_algorithms = self.config.stackingconfig.base_algorithms.clone();
for (alg_idx, algorithm) in base_algorithms.iter().enumerate() {
let labels = self.train_base_algorithm(&train_data, algorithm)?;
let test_labels = self.predict_base_algorithm(&test_data, algorithm, &labels)?;
for (i, &test_idx) in test_indices.iter().enumerate() {
if i < test_labels.len() {
base_predictions[[test_idx, alg_idx]] = test_labels[i] as f64;
}
}
}
}
let meta_labels = self.train_meta_clustering_algorithm(&base_predictions)?;
let individual_results = vec![]; let consensus_stats = self.calculate_stacking_consensus_stats(&meta_labels)?;
let diversity_metrics = self.calculate_stacking_diversity_metrics(&base_predictions)?;
Ok(EnsembleResult {
consensus_labels: meta_labels,
individual_results,
consensus_stats,
diversity_metrics,
ensemble_quality: 0.0, stability_score: 0.0, })
}
fn extract_meta_features(
&self,
data: ArrayView2<F>,
results: &EnsembleResult,
) -> Result<Array2<f64>> {
let n_features = self.config.meta_learning.n_meta_features;
let mut meta_features = Array2::zeros((1, n_features));
let n_samples = data.nrows() as f64;
let n_dims = data.ncols() as f64;
let data_f64 = data.mapv(|x| x.to_f64().unwrap_or(0.0));
meta_features[[0, 0]] = n_samples.ln();
meta_features[[0, 1]] = n_dims.ln();
meta_features[[0, 2]] = data_f64.var(0.0);
meta_features[[0, 3]] = calculate_intrinsic_dimensionality(&data_f64);
meta_features[[0, 4]] = calculate_clustering_tendency(&data_f64);
meta_features[[0, 5]] = results.diversity_metrics.average_diversity;
for i in 6..n_features {
meta_features[[0, i]] = calculate_advanced_meta_feature(&data_f64, i - 6);
}
Ok(meta_features)
}
fn train_meta_learner(
&mut self,
meta_features: &Array2<f64>,
base_results: &[ClusteringResult],
) -> Result<Array1<f64>> {
match &self.config.meta_learning.algorithm {
MetaLearningAlgorithm::NeuralNetwork { hidden_layers } => {
let hidden_layers = hidden_layers.clone();
self.train_neural_meta_learner(meta_features, base_results, &hidden_layers)
}
MetaLearningAlgorithm::RandomForest { n_trees, max_depth } => {
self.train_forest_meta_learner(meta_features, base_results, *n_trees, *max_depth)
}
MetaLearningAlgorithm::Linear { regularization } => {
self.train_linear_meta_learner(meta_features, base_results, *regularization)
}
_ => {
Ok(Array1::from_elem(
base_results.len(),
1.0 / base_results.len() as f64,
))
}
}
}
fn train_neural_meta_learner(
&mut self,
_meta_features: &Array2<f64>,
base_results: &[ClusteringResult],
_hidden_layers: &[usize],
) -> Result<Array1<f64>> {
let mut weights = Array1::zeros(base_results.len());
let quality_sum: f64 = base_results.iter().map(|r| r.quality_score.max(0.0)).sum();
if quality_sum > 0.0 {
for (i, result) in base_results.iter().enumerate() {
let normalized_quality = result.quality_score.max(0.0) / quality_sum;
weights[i] = 1.0 / (1.0 + (-5.0 * (normalized_quality - 0.5)).exp());
}
} else {
weights.fill(1.0 / base_results.len() as f64);
}
let weight_sum = weights.sum();
if weight_sum > 0.0 {
weights.mapv_inplace(|w| w / weight_sum);
}
Ok(weights)
}
fn train_forest_meta_learner(
&mut self,
_meta_features: &Array2<f64>,
base_results: &[ClusteringResult],
_n_trees: usize,
_max_depth: usize,
) -> Result<Array1<f64>> {
let mut weights = Array1::zeros(base_results.len());
for (i, result) in base_results.iter().enumerate() {
let efficiency_score = 1.0 / (1.0 + result.runtime);
let combined_score = result.quality_score * 0.7 + efficiency_score * 0.3;
weights[i] = combined_score.max(0.0);
}
let weight_sum = weights.sum();
if weight_sum > 0.0 {
weights.mapv_inplace(|w| w / weight_sum);
} else {
weights.fill(1.0 / base_results.len() as f64);
}
Ok(weights)
}
fn train_linear_meta_learner(
&mut self,
_meta_features: &Array2<f64>,
base_results: &[ClusteringResult],
regularization: f64,
) -> Result<Array1<f64>> {
let mut weights = Array1::zeros(base_results.len());
for (i, result) in base_results.iter().enumerate() {
let quality_with_reg =
result.quality_score - regularization * result.quality_score.powi(2);
weights[i] = quality_with_reg.max(0.0);
}
let weight_sum = weights.sum();
if weight_sum > 0.0 {
weights.mapv_inplace(|w| w / weight_sum);
} else {
weights.fill(1.0 / base_results.len() as f64);
}
Ok(weights)
}
fn weighted_meta_consensus(
&self,
base_results: &[ClusteringResult],
weights: &Array1<f64>,
n_samples: usize,
) -> Result<Array1<i32>> {
let mut consensus = Array1::<i32>::zeros(n_samples);
for i in 0..n_samples {
let mut vote_scores = HashMap::new();
for (result_idx, result) in base_results.iter().enumerate() {
if i < result.labels.len() {
let label = result.labels[i];
let weight = weights[result_idx];
*vote_scores.entry(label).or_insert(0.0) += weight;
}
}
let best_label = vote_scores
.into_iter()
.max_by(|(_, score_a), (_, score_b)| {
score_a.partial_cmp(score_b).unwrap_or(Ordering::Equal)
})
.map(|(label_, _)| label_)
.unwrap_or(0);
consensus[i] = best_label;
}
Ok(consensus)
}
fn mcmc_update_weights(
&self,
current_weights: &Array1<f64>,
_results: &EnsembleResult,
data: ArrayView2<F>,
) -> Result<Array1<f64>> {
let mut new_weights = current_weights.clone();
let mut rng = scirs2_core::random::thread_rng();
for weight in new_weights.iter_mut() {
let perturbation = rng.random_range(-0.05..0.05);
*weight = (*weight + perturbation).max(0.01).min(0.99);
}
let sum = new_weights.sum();
new_weights.mapv_inplace(|w| w / sum);
let accept_prob = rng.random::<f64>();
if accept_prob > 0.5 {
Ok(new_weights)
} else {
Ok(current_weights.clone())
}
}
fn bayesian_weighted_consensus(
&self,
base_results: &[ClusteringResult],
weights: &Array1<f64>,
n_samples: usize,
) -> Result<Array1<i32>> {
self.weighted_meta_consensus(base_results, weights, n_samples)
}
fn calculate_meta_quality(&self, data: ArrayView2<F>, result: &EnsembleResult) -> Result<f64> {
let data_f64 = data.mapv(|x| x.to_f64().unwrap_or(0.0));
silhouette_score(data_f64.view(), result.consensus_labels.view()).map_err(|e| e)
}
fn weighted_sample(&self, data: ArrayView2<F>, weights: &Array1<f64>) -> Result<Array2<F>> {
let n_samples = data.nrows();
let n_features = data.ncols();
if weights.len() != n_samples {
return Err(ClusteringError::InvalidInput(
"Weights array length must match number of samples".to_string(),
));
}
let dist = WeightedIndex::new(weights.iter().cloned()).map_err(|e| {
ClusteringError::InvalidInput(format!("Invalid weights for sampling: {}", e))
})?;
let mut rng = scirs2_core::random::thread_rng();
let mut sampled_data = Array2::zeros((n_samples, n_features));
for i in 0..n_samples {
let sampled_idx = dist.sample(&mut rng);
for j in 0..n_features {
sampled_data[[i, j]] = data[[sampled_idx, j]];
}
}
Ok(sampled_data)
}
fn train_weak_learner(&self, data: &Array2<F>) -> Result<ClusteringResult> {
let k = 2;
let n_clusters = k;
let labels = Array1::from_shape_fn(data.nrows(), |i| (i % k) as i32);
let mut parameters = HashMap::new();
parameters.insert("k".to_string(), k.to_string());
Ok(ClusteringResult::new(
labels,
"weak_kmeans".to_string(),
parameters,
0.5, 0.1, ))
}
fn calculate_clustering_error(
&self,
data: ArrayView2<F>,
result: &ClusteringResult,
weights: &Array1<f64>,
) -> Result<f64> {
let data_f64 = data.mapv(|x| x.to_f64().unwrap_or(0.0));
let silhouette = silhouette_score(data_f64.view(), result.labels.view()).unwrap_or(0.0);
let error_rate = (1.0 - silhouette) / 2.0;
Ok(error_rate.max(0.0).min(1.0))
}
fn update_sample_weights(
&self,
weights: &mut Array1<f64>,
result: &ClusteringResult,
learner_weight: f64,
data: ArrayView2<F>,
) -> Result<()> {
for (i, &label) in result.labels.iter().enumerate() {
if i < weights.len() {
weights[i] *= (learner_weight / 2.0).exp();
}
}
let weight_sum = weights.sum();
if weight_sum > 0.0 {
weights.mapv_inplace(|w| w / weight_sum);
}
Ok(())
}
fn combine_boosted_learners(
&self,
weak_learners: &[ClusteringResult],
learner_weights: &[f64],
n_samples: usize,
) -> Result<EnsembleResult> {
let mut consensus_labels = Array1::zeros(n_samples);
for i in 0..n_samples {
let mut vote_scores = HashMap::new();
for (learner_idx, learner) in weak_learners.iter().enumerate() {
if i < learner.labels.len() {
let label = learner.labels[i];
let weight = learner_weights[learner_idx];
*vote_scores.entry(label).or_insert(0.0) += weight;
}
}
let best_label = vote_scores
.into_iter()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
.map(|(label_, _)| label_)
.unwrap_or(0);
consensus_labels[i] = best_label;
}
Ok(EnsembleResult::new(
consensus_labels,
weak_learners.to_vec(),
ConsensusStatistics::new(
Array2::zeros((n_samples, n_samples)),
Array1::ones(n_samples),
vec![0.5; 10],
Array1::ones(n_samples),
),
DiversityMetrics::new(
0.5,
Array2::eye(weak_learners.len()),
HashMap::new(),
HashMap::new(),
),
0.5,
0.5,
))
}
fn train_base_algorithm(
&self,
data: &Array2<F>,
algorithm: &ClusteringAlgorithm,
) -> Result<Array1<i32>> {
Ok(Array1::<i32>::zeros(data.nrows()).mapv(|_| 0i32))
}
fn predict_base_algorithm(
&self,
data: &Array2<F>,
algorithm: &ClusteringAlgorithm,
trained_labels: &Array1<i32>,
) -> Result<Array1<i32>> {
Ok(Array1::<i32>::zeros(data.nrows()).mapv(|_| 0i32))
}
fn train_meta_clustering_algorithm(&self, predictions: &Array2<f64>) -> Result<Array1<i32>> {
Ok(Array1::<i32>::zeros(predictions.nrows()).mapv(|_| 0i32))
}
fn calculate_stacking_consensus_stats(
&self,
labels: &Array1<i32>,
) -> Result<ConsensusStatistics> {
let n_samples = labels.len();
Ok(ConsensusStatistics::new(
Array2::zeros((n_samples, n_samples)),
Array1::ones(n_samples),
vec![0.5; 10],
Array1::ones(n_samples),
))
}
fn calculate_stacking_diversity_metrics(
&self,
predictions: &Array2<f64>,
) -> Result<DiversityMetrics> {
Ok(DiversityMetrics::new(
0.5,
Array2::eye(predictions.ncols()),
HashMap::new(),
HashMap::new(),
))
}
}
fn calculate_intrinsic_dimensionality(data: &Array2<f64>) -> f64 {
let n_features = data.ncols() as f64;
(n_features / 2.0).ln()
}
fn calculate_clustering_tendency(data: &Array2<f64>) -> f64 {
let n_samples = data.nrows();
if n_samples < 2 {
return 0.5;
}
let total_variance = data.var(0.0);
let mean_variance = data
.mean_axis(scirs2_core::ndarray::Axis(0))
.expect("Operation failed")
.var(0.0);
if total_variance > 0.0 {
(mean_variance / total_variance).min(1.0)
} else {
0.5
}
}
fn calculate_advanced_meta_feature(data: &Array2<f64>, feature_index: usize) -> f64 {
match feature_index {
0 => data.mean_or(0.0),
1 => data.std(0.0),
2 => data.len() as f64,
_ => 0.5, }
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_advanced_ensemble_config() {
let config = AdvancedEnsembleConfig {
meta_learning: MetaLearningConfig {
n_meta_features: 10,
learning_rate: 0.01,
n_iterations: 100,
algorithm: MetaLearningAlgorithm::Linear {
regularization: 0.1,
},
validation_split: 0.2,
},
bayesian_averaging: BayesianAveragingConfig {
prior_alpha: 1.0,
prior_beta: 1.0,
n_samples: 1000,
burn_in: 100,
update_method: PosteriorUpdateMethod::MetropolisHastings,
adaptive_sampling: true,
},
genetic_optimization: GeneticOptimizationConfig {
population_size: 50,
n_generations: 20,
crossover_prob: 0.8,
mutation_prob: 0.1,
selection_method: SelectionMethod::Tournament { tournament_size: 3 },
elite_percentage: 0.1,
fitness_function: FitnessFunction::Silhouette,
},
boostingconfig: BoostingConfig {
n_rounds: 10,
learning_rate: 1.0,
reweighting_strategy: ReweightingStrategy::Exponential,
error_function: ErrorFunction::DisagreementRate,
adaptive_boosting: true,
},
stackingconfig: StackingConfig {
base_algorithms: vec![ClusteringAlgorithm::KMeans { k_range: (2, 5) }],
meta_algorithm: MetaClusteringAlgorithm::Hierarchical {
linkage: "ward".to_string(),
},
cv_folds: 5,
blending_ratio: 0.5,
feature_engineering: true,
},
uncertainty_quantification: true,
};
assert_eq!(config.meta_learning.n_meta_features, 10);
assert_eq!(config.bayesian_averaging.n_samples, 1000);
assert_eq!(config.genetic_optimization.population_size, 50);
assert_eq!(config.boostingconfig.n_rounds, 10);
assert_eq!(config.stackingconfig.cv_folds, 5);
}
#[test]
fn test_genetic_optimizer() {
let config = GeneticOptimizationConfig {
population_size: 10,
n_generations: 5,
crossover_prob: 0.8,
mutation_prob: 0.1,
selection_method: SelectionMethod::Tournament { tournament_size: 3 },
elite_percentage: 0.2,
fitness_function: FitnessFunction::Silhouette,
};
let mut optimizer = GeneticOptimizer::new(config);
assert!(optimizer.initialize_population().is_ok());
assert_eq!(optimizer.population.len(), 10);
}
}