use crate::metrics::coherence::lexical_coherence::config::{
SemanticAnalysisConfig, SemanticRelationshipType,
};
use crate::metrics::coherence::lexical_coherence::results::{
LexicalItem, SemanticAnalysisResult, SemanticCluster, SemanticRelationship,
};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum SemanticAnalysisError {
#[error("Failed to load semantic lexicon: {0}")]
LexiconLoadError(String),
#[error("Word sense disambiguation failed: {0}")]
DisambiguationError(String),
#[error("Semantic similarity calculation error: {0}")]
SimilarityError(String),
#[error("Invalid semantic configuration: {0}")]
ConfigurationError(String),
}
#[derive(Debug)]
pub struct SemanticAnalyzer {
config: SemanticAnalysisConfig,
wordnet_relations: HashMap<String, HashMap<String, Vec<String>>>,
word_embeddings: HashMap<String, Vec<f64>>,
semantic_networks: HashMap<String, SemanticNetwork>,
similarity_cache: HashMap<(String, String), f64>,
relationship_cache: HashMap<(String, String), Vec<SemanticRelationship>>,
word_sense_cache: HashMap<String, Vec<WordSense>>,
word_sense_disambiguator: WordSenseDisambiguator,
similarity_calculator: SimilarityCalculator,
relationship_classifier: RelationshipClassifier,
}
#[derive(Debug, Clone)]
struct SemanticNetwork {
nodes: HashMap<String, SemanticNode>,
edges: HashMap<(String, String), SemanticEdge>,
clusters: Vec<SemanticCluster>,
}
#[derive(Debug, Clone)]
struct SemanticNode {
word: String,
senses: Vec<WordSense>,
frequency: f64,
centrality: f64,
semantic_features: Vec<String>,
}
#[derive(Debug, Clone)]
struct SemanticEdge {
source: String,
target: String,
relationship_type: SemanticRelationshipType,
strength: f64,
confidence: f64,
distance: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WordSense {
pub sense_id: String,
pub definition: String,
pub examples: Vec<String>,
pub frequency: f64,
pub semantic_features: Vec<String>,
pub hypernyms: Vec<String>,
pub hyponyms: Vec<String>,
pub synonyms: Vec<String>,
pub antonyms: Vec<String>,
pub meronyms: Vec<String>,
pub holonyms: Vec<String>,
}
#[derive(Debug)]
struct WordSenseDisambiguator {
context_window: usize,
disambiguation_threshold: f64,
sense_embeddings: HashMap<String, Vec<f64>>,
context_similarity_cache: HashMap<String, f64>,
}
#[derive(Debug)]
struct SimilarityCalculator {
embedding_dim: usize,
similarity_metrics: Vec<SimilarityMetric>,
weight_combinations: HashMap<String, f64>,
}
#[derive(Debug)]
struct RelationshipClassifier {
classification_rules: HashMap<SemanticRelationshipType, ClassificationRule>,
feature_extractors: Vec<FeatureExtractor>,
confidence_thresholds: HashMap<SemanticRelationshipType, f64>,
}
#[derive(Debug, Clone)]
enum SimilarityMetric {
Cosine,
Jaccard,
PathBased,
InformationContent,
Wu_Palmer,
Leacock_Chodorow,
Resnik,
JiangConrath,
Lin,
}
#[derive(Debug, Clone)]
struct ClassificationRule {
feature_patterns: Vec<String>,
weight_vector: Vec<f64>,
threshold: f64,
confidence_adjustment: f64,
}
#[derive(Debug, Clone)]
struct FeatureExtractor {
name: String,
extraction_function: fn(&str, &str, &HashMap<String, WordSense>) -> Vec<f64>,
}
impl SemanticAnalyzer {
pub fn new(config: SemanticAnalysisConfig) -> Result<Self, SemanticAnalysisError> {
let mut analyzer = SemanticAnalyzer {
config: config.clone(),
wordnet_relations: HashMap::new(),
word_embeddings: HashMap::new(),
semantic_networks: HashMap::new(),
similarity_cache: HashMap::new(),
relationship_cache: HashMap::new(),
word_sense_cache: HashMap::new(),
word_sense_disambiguator: WordSenseDisambiguator::new(&config)?,
similarity_calculator: SimilarityCalculator::new(&config)?,
relationship_classifier: RelationshipClassifier::new(&config)?,
};
analyzer.initialize_semantic_resources()?;
Ok(analyzer)
}
pub fn analyze_semantic_relationships(
&mut self,
lexical_items: &[LexicalItem],
context: &[String],
) -> Result<SemanticAnalysisResult, SemanticAnalysisError> {
let disambiguated_items = self.disambiguate_word_senses(lexical_items, context)?;
let similarity_matrix = self.calculate_similarity_matrix(&disambiguated_items)?;
let relationships =
self.identify_semantic_relationships(&disambiguated_items, &similarity_matrix)?;
let semantic_network = self.build_semantic_network(&disambiguated_items, &relationships)?;
let network_metrics = self.analyze_semantic_network(&semantic_network)?;
let cohesion_scores = self.calculate_semantic_cohesion(&relationships, &network_metrics)?;
let clusters = self.generate_semantic_clusters(&semantic_network, &similarity_matrix)?;
Ok(SemanticAnalysisResult {
disambiguated_items,
similarity_matrix,
relationships,
semantic_network: network_metrics,
cohesion_scores,
clusters,
analysis_metadata: self.generate_analysis_metadata(),
})
}
fn initialize_semantic_resources(&mut self) -> Result<(), SemanticAnalysisError> {
if self.config.use_wordnet {
self.load_wordnet_relations()?;
}
if self.config.use_embeddings {
self.load_word_embeddings()?;
}
if self.config.build_semantic_networks {
self.initialize_semantic_networks()?;
}
Ok(())
}
fn load_wordnet_relations(&mut self) -> Result<(), SemanticAnalysisError> {
self.wordnet_relations = HashMap::new();
let basic_relations = vec![
(
"good",
vec![
("great", "synonym"),
("bad", "antonym"),
("excellent", "synonym"),
],
),
(
"big",
vec![
("large", "synonym"),
("small", "antonym"),
("huge", "synonym"),
],
),
(
"happy",
vec![
("joyful", "synonym"),
("sad", "antonym"),
("cheerful", "synonym"),
],
),
];
for (word, relations) in basic_relations {
let mut word_relations = HashMap::new();
for (related_word, relation_type) in relations {
word_relations
.entry(relation_type.to_string())
.or_insert_with(Vec::new)
.push(related_word.to_string());
}
self.wordnet_relations
.insert(word.to_string(), word_relations);
}
Ok(())
}
fn load_word_embeddings(&mut self) -> Result<(), SemanticAnalysisError> {
self.word_embeddings = HashMap::new();
let common_words = vec![
"the", "and", "or", "but", "good", "bad", "big", "small", "happy", "sad", "run",
"walk", "fast", "slow", "time", "day",
];
for word in common_words {
let embedding: Vec<f64> = (0..self.config.embedding_dimension)
.map(|_| rand::random::<f64>() * 2.0 - 1.0)
.collect();
self.word_embeddings.insert(word.to_string(), embedding);
}
Ok(())
}
fn initialize_semantic_networks(&mut self) -> Result<(), SemanticAnalysisError> {
self.semantic_networks = HashMap::new();
Ok(())
}
fn disambiguate_word_senses(
&mut self,
lexical_items: &[LexicalItem],
context: &[String],
) -> Result<Vec<LexicalItem>, SemanticAnalysisError> {
let mut disambiguated_items = Vec::new();
for item in lexical_items {
let disambiguated_item = if self.config.perform_disambiguation {
self.word_sense_disambiguator.disambiguate(item, context)?
} else {
item.clone()
};
disambiguated_items.push(disambiguated_item);
}
Ok(disambiguated_items)
}
fn calculate_similarity_matrix(
&mut self,
lexical_items: &[LexicalItem],
) -> Result<Vec<Vec<f64>>, SemanticAnalysisError> {
let n = lexical_items.len();
let mut matrix = vec![vec![0.0; n]; n];
for i in 0..n {
for j in i..n {
if i == j {
matrix[i][j] = 1.0;
} else {
let similarity = self
.similarity_calculator
.calculate_similarity(&lexical_items[i], &lexical_items[j])?;
matrix[i][j] = similarity;
matrix[j][i] = similarity;
}
}
}
Ok(matrix)
}
fn identify_semantic_relationships(
&mut self,
lexical_items: &[LexicalItem],
similarity_matrix: &[Vec<f64>],
) -> Result<Vec<SemanticRelationship>, SemanticAnalysisError> {
let mut relationships = Vec::new();
for i in 0..lexical_items.len() {
for j in (i + 1)..lexical_items.len() {
if similarity_matrix[i][j] >= self.config.relationship_threshold {
let relationship = self.relationship_classifier.classify_relationship(
&lexical_items[i],
&lexical_items[j],
similarity_matrix[i][j],
)?;
if relationship.confidence >= self.config.confidence_threshold {
relationships.push(relationship);
}
}
}
}
Ok(relationships)
}
fn build_semantic_network(
&mut self,
lexical_items: &[LexicalItem],
relationships: &[SemanticRelationship],
) -> Result<SemanticNetwork, SemanticAnalysisError> {
let mut network = SemanticNetwork {
nodes: HashMap::new(),
edges: HashMap::new(),
clusters: Vec::new(),
};
for item in lexical_items {
let node = SemanticNode {
word: item.word.clone(),
senses: item.word_senses.clone(),
frequency: item.frequency,
centrality: 0.0, semantic_features: item.semantic_features.clone(),
};
network.nodes.insert(item.word.clone(), node);
}
for relationship in relationships {
let edge = SemanticEdge {
source: relationship.source_word.clone(),
target: relationship.target_word.clone(),
relationship_type: relationship.relationship_type.clone(),
strength: relationship.strength,
confidence: relationship.confidence,
distance: relationship.distance,
};
network.edges.insert(
(
relationship.source_word.clone(),
relationship.target_word.clone(),
),
edge,
);
}
Ok(network)
}
fn analyze_semantic_network(
&self,
network: &SemanticNetwork,
) -> Result<HashMap<String, f64>, SemanticAnalysisError> {
let mut metrics = HashMap::new();
metrics.insert("node_count".to_string(), network.nodes.len() as f64);
metrics.insert("edge_count".to_string(), network.edges.len() as f64);
if !network.nodes.is_empty() {
let density = network.edges.len() as f64
/ (network.nodes.len() * (network.nodes.len() - 1) / 2) as f64;
metrics.insert("density".to_string(), density);
}
let centrality_scores = self.calculate_centrality_measures(network)?;
for (measure, score) in centrality_scores {
metrics.insert(format!("centrality_{}", measure), score);
}
let clustering_coefficient = self.calculate_clustering_coefficient(network)?;
metrics.insert("clustering_coefficient".to_string(), clustering_coefficient);
Ok(metrics)
}
fn calculate_semantic_cohesion(
&self,
relationships: &[SemanticRelationship],
network_metrics: &HashMap<String, f64>,
) -> Result<HashMap<String, f64>, SemanticAnalysisError> {
let mut cohesion_scores = HashMap::new();
let total_strength: f64 = relationships.iter().map(|r| r.strength).sum();
let avg_strength = if !relationships.is_empty() {
total_strength / relationships.len() as f64
} else {
0.0
};
cohesion_scores.insert("overall_cohesion".to_string(), avg_strength);
let mut type_groups: HashMap<String, Vec<f64>> = HashMap::new();
for relationship in relationships {
type_groups
.entry(format!("{:?}", relationship.relationship_type))
.or_default()
.push(relationship.strength);
}
for (rel_type, strengths) in type_groups {
let avg_type_strength = strengths.iter().sum::<f64>() / strengths.len() as f64;
cohesion_scores.insert(format!("{}_cohesion", rel_type), avg_type_strength);
}
if let Some(density) = network_metrics.get("density") {
cohesion_scores.insert("network_cohesion".to_string(), *density);
}
Ok(cohesion_scores)
}
fn generate_semantic_clusters(
&self,
network: &SemanticNetwork,
similarity_matrix: &[Vec<f64>],
) -> Result<Vec<SemanticCluster>, SemanticAnalysisError> {
let mut clusters = Vec::new();
if network.nodes.is_empty() {
return Ok(clusters);
}
let words: Vec<String> = network.nodes.keys().cloned().collect();
let mut assigned = HashSet::new();
let mut cluster_id = 0;
for i in 0..words.len() {
if assigned.contains(&i) {
continue;
}
let mut cluster_words = vec![words[i].clone()];
assigned.insert(i);
for j in (i + 1)..words.len() {
if assigned.contains(&j) {
continue;
}
if similarity_matrix[i][j] >= self.config.clustering_threshold {
cluster_words.push(words[j].clone());
assigned.insert(j);
}
}
if cluster_words.len() > 1 {
let avg_coherence =
self.calculate_cluster_coherence(&cluster_words, similarity_matrix, &words)?;
clusters.push(SemanticCluster {
cluster_id,
words: cluster_words,
coherence_score: avg_coherence,
size: cluster_words.len(),
centroid_word: None, semantic_theme: None, });
cluster_id += 1;
}
}
Ok(clusters)
}
fn calculate_centrality_measures(
&self,
network: &SemanticNetwork,
) -> Result<HashMap<String, f64>, SemanticAnalysisError> {
let mut measures = HashMap::new();
let max_degree = network
.nodes
.iter()
.map(|(word, _)| {
network
.edges
.iter()
.filter(|((source, target), _)| source == word || target == word)
.count()
})
.max()
.unwrap_or(0) as f64;
measures.insert("max_degree".to_string(), max_degree);
let total_degree: usize = network
.nodes
.iter()
.map(|(word, _)| {
network
.edges
.iter()
.filter(|((source, target), _)| source == word || target == word)
.count()
})
.sum();
let avg_degree = if !network.nodes.is_empty() {
total_degree as f64 / network.nodes.len() as f64
} else {
0.0
};
measures.insert("avg_degree".to_string(), avg_degree);
Ok(measures)
}
fn calculate_clustering_coefficient(
&self,
network: &SemanticNetwork,
) -> Result<f64, SemanticAnalysisError> {
if network.nodes.len() < 3 {
return Ok(0.0);
}
let mut total_clustering = 0.0;
let mut valid_nodes = 0;
for word in network.nodes.keys() {
let neighbors: Vec<&String> = network
.edges
.iter()
.filter_map(|((source, target), _)| {
if source == word {
Some(target)
} else if target == word {
Some(source)
} else {
None
}
})
.collect();
if neighbors.len() > 1 {
let possible_edges = neighbors.len() * (neighbors.len() - 1) / 2;
let actual_edges = neighbors
.iter()
.enumerate()
.flat_map(|(i, &n1)| neighbors[i + 1..].iter().map(move |&n2| (n1, n2)))
.filter(|(n1, n2)| {
network.edges.contains_key(&((*n1).clone(), (*n2).clone()))
|| network.edges.contains_key(&((*n2).clone(), (*n1).clone()))
})
.count();
total_clustering += actual_edges as f64 / possible_edges as f64;
valid_nodes += 1;
}
}
Ok(if valid_nodes > 0 {
total_clustering / valid_nodes as f64
} else {
0.0
})
}
fn calculate_cluster_coherence(
&self,
cluster_words: &[String],
similarity_matrix: &[Vec<f64>],
all_words: &[String],
) -> Result<f64, SemanticAnalysisError> {
if cluster_words.len() < 2 {
return Ok(0.0);
}
let mut total_similarity = 0.0;
let mut pair_count = 0;
for i in 0..cluster_words.len() {
for j in (i + 1)..cluster_words.len() {
if let (Some(idx_i), Some(idx_j)) = (
all_words.iter().position(|w| w == &cluster_words[i]),
all_words.iter().position(|w| w == &cluster_words[j]),
) {
total_similarity += similarity_matrix[idx_i][idx_j];
pair_count += 1;
}
}
}
Ok(if pair_count > 0 {
total_similarity / pair_count as f64
} else {
0.0
})
}
fn generate_analysis_metadata(&self) -> HashMap<String, String> {
let mut metadata = HashMap::new();
metadata.insert("analyzer_version".to_string(), "1.0.0".to_string());
metadata.insert(
"disambiguation_enabled".to_string(),
self.config.perform_disambiguation.to_string(),
);
metadata.insert(
"embedding_dimension".to_string(),
self.config.embedding_dimension.to_string(),
);
metadata.insert(
"relationship_threshold".to_string(),
self.config.relationship_threshold.to_string(),
);
metadata.insert(
"confidence_threshold".to_string(),
self.config.confidence_threshold.to_string(),
);
metadata
}
}
impl WordSenseDisambiguator {
fn new(config: &SemanticAnalysisConfig) -> Result<Self, SemanticAnalysisError> {
Ok(WordSenseDisambiguator {
context_window: config.context_window_size,
disambiguation_threshold: config.disambiguation_threshold,
sense_embeddings: HashMap::new(),
context_similarity_cache: HashMap::new(),
})
}
fn disambiguate(
&mut self,
item: &LexicalItem,
context: &[String],
) -> Result<LexicalItem, SemanticAnalysisError> {
if item.word_senses.len() <= 1 {
return Ok(item.clone());
}
let context_vector = self.extract_context_vector(item, context)?;
let mut best_sense = item.word_senses[0].clone();
let mut best_score = 0.0;
for sense in &item.word_senses {
let sense_score = self.calculate_sense_context_similarity(sense, &context_vector)?;
if sense_score > best_score {
best_score = sense_score;
best_sense = sense.clone();
}
}
let mut disambiguated_item = item.clone();
if best_score >= self.disambiguation_threshold {
disambiguated_item.word_senses = vec![best_sense];
}
Ok(disambiguated_item)
}
fn extract_context_vector(
&self,
item: &LexicalItem,
context: &[String],
) -> Result<Vec<f64>, SemanticAnalysisError> {
let context_words: Vec<String> = context
.iter()
.flat_map(|sentence| sentence.split_whitespace())
.filter(|word| word.to_lowercase() != item.word.to_lowercase())
.map(|word| word.to_lowercase())
.collect();
let mut context_vector = vec![0.0; 100];
for (i, word) in context_words.iter().enumerate() {
let hash = word.len() % context_vector.len();
context_vector[hash] += 1.0;
}
let magnitude: f64 = context_vector.iter().map(|x| x * x).sum::<f64>().sqrt();
if magnitude > 0.0 {
for value in context_vector.iter_mut() {
*value /= magnitude;
}
}
Ok(context_vector)
}
fn calculate_sense_context_similarity(
&self,
sense: &WordSense,
context_vector: &[f64],
) -> Result<f64, SemanticAnalysisError> {
let sense_features: Vec<String> = sense
.definition
.split_whitespace()
.chain(sense.examples.iter().flat_map(|ex| ex.split_whitespace()))
.map(|word| word.to_lowercase())
.collect();
let mut sense_vector = vec![0.0; context_vector.len()];
for word in sense_features {
let hash = word.len() % sense_vector.len();
sense_vector[hash] += 1.0;
}
let magnitude: f64 = sense_vector.iter().map(|x| x * x).sum::<f64>().sqrt();
if magnitude > 0.0 {
for value in sense_vector.iter_mut() {
*value /= magnitude;
}
}
let dot_product: f64 = context_vector
.iter()
.zip(sense_vector.iter())
.map(|(a, b)| a * b)
.sum();
Ok(dot_product)
}
}
impl SimilarityCalculator {
fn new(config: &SemanticAnalysisConfig) -> Result<Self, SemanticAnalysisError> {
Ok(SimilarityCalculator {
embedding_dim: config.embedding_dimension,
similarity_metrics: vec![
SimilarityMetric::Cosine,
SimilarityMetric::Jaccard,
SimilarityMetric::PathBased,
],
weight_combinations: HashMap::from([
("cosine".to_string(), 0.4),
("jaccard".to_string(), 0.3),
("path_based".to_string(), 0.3),
]),
})
}
fn calculate_similarity(
&mut self,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
let mut total_similarity = 0.0;
let mut total_weight = 0.0;
for metric in &self.similarity_metrics {
let weight = self.get_metric_weight(metric);
let similarity = self.calculate_metric_similarity(metric, item1, item2)?;
total_similarity += similarity * weight;
total_weight += weight;
}
Ok(if total_weight > 0.0 {
total_similarity / total_weight
} else {
0.0
})
}
fn get_metric_weight(&self, metric: &SimilarityMetric) -> f64 {
let key = match metric {
SimilarityMetric::Cosine => "cosine",
SimilarityMetric::Jaccard => "jaccard",
SimilarityMetric::PathBased => "path_based",
_ => "default",
};
self.weight_combinations.get(key).copied().unwrap_or(0.1)
}
fn calculate_metric_similarity(
&self,
metric: &SimilarityMetric,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
match metric {
SimilarityMetric::Cosine => self.calculate_cosine_similarity(item1, item2),
SimilarityMetric::Jaccard => self.calculate_jaccard_similarity(item1, item2),
SimilarityMetric::PathBased => self.calculate_path_based_similarity(item1, item2),
_ => Ok(0.0), }
}
fn calculate_cosine_similarity(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
let features1: HashSet<String> = item1.semantic_features.iter().cloned().collect();
let features2: HashSet<String> = item2.semantic_features.iter().cloned().collect();
if features1.is_empty() && features2.is_empty() {
return Ok(0.0);
}
let intersection = features1.intersection(&features2).count() as f64;
let magnitude1 = (features1.len() as f64).sqrt();
let magnitude2 = (features2.len() as f64).sqrt();
if magnitude1 * magnitude2 > 0.0 {
Ok(intersection / (magnitude1 * magnitude2))
} else {
Ok(0.0)
}
}
fn calculate_jaccard_similarity(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
let features1: HashSet<String> = item1.semantic_features.iter().cloned().collect();
let features2: HashSet<String> = item2.semantic_features.iter().cloned().collect();
let intersection = features1.intersection(&features2).count() as f64;
let union = features1.union(&features2).count() as f64;
Ok(if union > 0.0 {
intersection / union
} else {
0.0
})
}
fn calculate_path_based_similarity(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
if item1.word_senses.is_empty() || item2.word_senses.is_empty() {
return Ok(0.0);
}
let mut max_similarity = 0.0;
for sense1 in &item1.word_senses {
for sense2 in &item2.word_senses {
let similarity = self.calculate_sense_path_similarity(sense1, sense2)?;
if similarity > max_similarity {
max_similarity = similarity;
}
}
}
Ok(max_similarity)
}
fn calculate_sense_path_similarity(
&self,
sense1: &WordSense,
sense2: &WordSense,
) -> Result<f64, SemanticAnalysisError> {
if sense1.synonyms.contains(&sense2.sense_id) || sense2.synonyms.contains(&sense1.sense_id)
{
return Ok(0.9);
}
if sense1.hypernyms.contains(&sense2.sense_id)
|| sense2.hypernyms.contains(&sense1.sense_id)
|| sense1.hyponyms.contains(&sense2.sense_id)
|| sense2.hyponyms.contains(&sense1.sense_id)
{
return Ok(0.7);
}
let shared_hypernyms = sense1
.hypernyms
.iter()
.filter(|h| sense2.hypernyms.contains(h))
.count();
if shared_hypernyms > 0 {
return Ok(0.5 * (shared_hypernyms as f64).sqrt());
}
Ok(0.0)
}
}
impl RelationshipClassifier {
fn new(config: &SemanticAnalysisConfig) -> Result<Self, SemanticAnalysisError> {
Ok(RelationshipClassifier {
classification_rules: HashMap::new(),
feature_extractors: Vec::new(),
confidence_thresholds: HashMap::new(),
})
}
fn classify_relationship(
&mut self,
item1: &LexicalItem,
item2: &LexicalItem,
similarity_score: f64,
) -> Result<SemanticRelationship, SemanticAnalysisError> {
let features = self.extract_relationship_features(item1, item2, similarity_score)?;
let relationship_type = self.classify_relationship_type(&features)?;
let confidence = self.calculate_classification_confidence(&features, &relationship_type)?;
let strength = self.calculate_relationship_strength(item1, item2, similarity_score)?;
let distance = self.calculate_semantic_distance(item1, item2)?;
Ok(SemanticRelationship {
source_word: item1.word.clone(),
target_word: item2.word.clone(),
relationship_type,
strength,
confidence,
distance,
evidence: features,
context_positions: vec![], })
}
fn extract_relationship_features(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
similarity_score: f64,
) -> Result<Vec<String>, SemanticAnalysisError> {
let mut features = Vec::new();
if similarity_score > 0.8 {
features.push("high_similarity".to_string());
} else if similarity_score > 0.5 {
features.push("medium_similarity".to_string());
} else {
features.push("low_similarity".to_string());
}
for sense1 in &item1.word_senses {
for sense2 in &item2.word_senses {
if sense1.synonyms.contains(&sense2.sense_id) {
features.push("synonym_relationship".to_string());
}
if sense1.antonyms.contains(&sense2.sense_id) {
features.push("antonym_relationship".to_string());
}
if sense1.hypernyms.contains(&sense2.sense_id) {
features.push("hypernym_relationship".to_string());
}
if sense1.hyponyms.contains(&sense2.sense_id) {
features.push("hyponym_relationship".to_string());
}
if sense1.meronyms.contains(&sense2.sense_id) {
features.push("meronym_relationship".to_string());
}
}
}
if self.is_morphologically_related(&item1.word, &item2.word) {
features.push("morphological_relationship".to_string());
}
let freq_ratio = (item1.frequency / item2.frequency).max(item2.frequency / item1.frequency);
if freq_ratio > 2.0 {
features.push("frequency_mismatch".to_string());
} else {
features.push("frequency_similar".to_string());
}
Ok(features)
}
fn classify_relationship_type(
&self,
features: &[String],
) -> Result<SemanticRelationshipType, SemanticAnalysisError> {
if features.contains(&"synonym_relationship".to_string()) {
Ok(SemanticRelationshipType::Synonymy)
} else if features.contains(&"antonym_relationship".to_string()) {
Ok(SemanticRelationshipType::Antonymy)
} else if features.contains(&"hypernym_relationship".to_string())
|| features.contains(&"hyponym_relationship".to_string())
{
Ok(SemanticRelationshipType::Hyponymy)
} else if features.contains(&"meronym_relationship".to_string()) {
Ok(SemanticRelationshipType::Meronymy)
} else if features.contains(&"morphological_relationship".to_string()) {
Ok(SemanticRelationshipType::Morphological)
} else if features.contains(&"high_similarity".to_string()) {
Ok(SemanticRelationshipType::Association)
} else {
Ok(SemanticRelationshipType::Sequential)
}
}
fn calculate_classification_confidence(
&self,
features: &[String],
relationship_type: &SemanticRelationshipType,
) -> Result<f64, SemanticAnalysisError> {
let evidence_count = features.len() as f64;
let base_confidence = match relationship_type {
SemanticRelationshipType::Synonymy => 0.9,
SemanticRelationshipType::Antonymy => 0.9,
SemanticRelationshipType::Hyponymy => 0.8,
SemanticRelationshipType::Meronymy => 0.8,
SemanticRelationshipType::Morphological => 0.7,
SemanticRelationshipType::Association => 0.6,
_ => 0.5,
};
let confidence = base_confidence * (1.0 + evidence_count * 0.1).min(1.0);
Ok(confidence)
}
fn calculate_relationship_strength(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
similarity_score: f64,
) -> Result<f64, SemanticAnalysisError> {
let frequency_factor = (item1.frequency * item2.frequency).sqrt();
let sense_factor = if !item1.word_senses.is_empty() && !item2.word_senses.is_empty() {
1.0 / (item1.word_senses.len() * item2.word_senses.len()) as f64
} else {
0.5
};
let strength = similarity_score * 0.7 + frequency_factor * 0.2 + sense_factor * 0.1;
Ok(strength.min(1.0))
}
fn calculate_semantic_distance(
&self,
item1: &LexicalItem,
item2: &LexicalItem,
) -> Result<f64, SemanticAnalysisError> {
let position_distance =
if let (Some(pos1), Some(pos2)) = (item1.positions.first(), item2.positions.first()) {
(pos1.0 as f64 - pos2.0 as f64).abs()
} else {
0.0
};
let feature_overlap = item1
.semantic_features
.iter()
.filter(|feature| item2.semantic_features.contains(feature))
.count() as f64;
let max_features = item1
.semantic_features
.len()
.max(item2.semantic_features.len()) as f64;
let feature_distance = if max_features > 0.0 {
1.0 - (feature_overlap / max_features)
} else {
1.0
};
let distance = (position_distance * 0.3 + feature_distance * 0.7).min(1.0);
Ok(distance)
}
fn is_morphologically_related(&self, word1: &str, word2: &str) -> bool {
let word1_lower = word1.to_lowercase();
let word2_lower = word2.to_lowercase();
let common_suffixes = vec!["ing", "ed", "er", "est", "ly", "tion", "sion", "ness"];
for suffix in common_suffixes {
if word1_lower.ends_with(suffix) {
let stem1 = &word1_lower[..word1_lower.len() - suffix.len()];
if word2_lower.starts_with(stem1) || word2_lower == stem1 {
return true;
}
}
if word2_lower.ends_with(suffix) {
let stem2 = &word2_lower[..word2_lower.len() - suffix.len()];
if word1_lower.starts_with(stem2) || word1_lower == stem2 {
return true;
}
}
}
false
}
}
use scirs2_core::random::{rng, Random};
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::coherence::lexical_coherence::config::SemanticAnalysisConfig;
use crate::metrics::coherence::lexical_coherence::results::{LexicalItem, WordSense};
#[test]
fn test_semantic_analyzer_creation() {
let config = SemanticAnalysisConfig::default();
let analyzer = SemanticAnalyzer::new(config);
assert!(analyzer.is_ok());
}
#[test]
fn test_word_sense_disambiguation() {
let config = SemanticAnalysisConfig::default();
let mut analyzer = SemanticAnalyzer::new(config).expect("Semantic Analyzer should succeed");
let lexical_item = LexicalItem {
word: "bank".to_string(),
lemma: "bank".to_string(),
positions: vec![(0, 4)],
frequency: 1.0,
word_senses: vec![
WordSense {
sense_id: "bank_1".to_string(),
definition: "financial institution".to_string(),
examples: vec!["I went to the bank".to_string()],
frequency: 0.7,
semantic_features: vec!["finance".to_string(), "institution".to_string()],
hypernyms: vec!["institution".to_string()],
hyponyms: vec![],
synonyms: vec![],
antonyms: vec![],
meronyms: vec![],
holonyms: vec![],
},
WordSense {
sense_id: "bank_2".to_string(),
definition: "edge of river".to_string(),
examples: vec!["by the river bank".to_string()],
frequency: 0.3,
semantic_features: vec!["geography".to_string(), "water".to_string()],
hypernyms: vec!["location".to_string()],
hyponyms: vec![],
synonyms: vec![],
antonyms: vec![],
meronyms: vec![],
holonyms: vec![],
},
],
semantic_features: vec!["finance".to_string(), "geography".to_string()],
};
let context = vec!["The money is in the bank account".to_string()];
let result = analyzer.analyze_semantic_relationships(&[lexical_item], &context);
assert!(result.is_ok());
}
#[test]
fn test_similarity_calculation() {
let config = SemanticAnalysisConfig::default();
let mut calculator = SimilarityCalculator::new(&config).expect("Similarity Calculator should succeed");
let item1 = LexicalItem {
word: "good".to_string(),
lemma: "good".to_string(),
positions: vec![(0, 4)],
frequency: 1.0,
word_senses: vec![],
semantic_features: vec!["positive".to_string(), "quality".to_string()],
};
let item2 = LexicalItem {
word: "excellent".to_string(),
lemma: "excellent".to_string(),
positions: vec![(5, 14)],
frequency: 1.0,
word_senses: vec![],
semantic_features: vec!["positive".to_string(), "quality".to_string()],
};
let similarity = calculator.calculate_similarity(&item1, &item2).expect("similarity calculation should succeed");
assert!(similarity > 0.0);
assert!(similarity <= 1.0);
}
#[test]
fn test_relationship_classification() {
let config = SemanticAnalysisConfig::default();
let mut classifier = RelationshipClassifier::new(&config).expect("Relationship Classifier should succeed");
let item1 = LexicalItem {
word: "big".to_string(),
lemma: "big".to_string(),
positions: vec![(0, 3)],
frequency: 1.0,
word_senses: vec![WordSense {
sense_id: "big_1".to_string(),
definition: "large in size".to_string(),
examples: vec![],
frequency: 1.0,
semantic_features: vec!["size".to_string()],
hypernyms: vec![],
hyponyms: vec![],
synonyms: vec!["large_1".to_string()],
antonyms: vec!["small_1".to_string()],
meronyms: vec![],
holonyms: vec![],
}],
semantic_features: vec!["size".to_string()],
};
let item2 = LexicalItem {
word: "small".to_string(),
lemma: "small".to_string(),
positions: vec![(4, 9)],
frequency: 1.0,
word_senses: vec![WordSense {
sense_id: "small_1".to_string(),
definition: "little in size".to_string(),
examples: vec![],
frequency: 1.0,
semantic_features: vec!["size".to_string()],
hypernyms: vec![],
hyponyms: vec![],
synonyms: vec!["little_1".to_string()],
antonyms: vec!["big_1".to_string()],
meronyms: vec![],
holonyms: vec![],
}],
semantic_features: vec!["size".to_string()],
};
let relationship = classifier
.classify_relationship(&item1, &item2, 0.3)
.expect("operation should succeed");
assert_eq!(relationship.source_word, "big");
assert_eq!(relationship.target_word, "small");
}
#[test]
fn test_semantic_network_building() {
let config = SemanticAnalysisConfig::default();
let mut analyzer = SemanticAnalyzer::new(config).expect("Semantic Analyzer should succeed");
let lexical_items = vec![
LexicalItem {
word: "good".to_string(),
lemma: "good".to_string(),
positions: vec![(0, 4)],
frequency: 1.0,
word_senses: vec![],
semantic_features: vec!["positive".to_string()],
},
LexicalItem {
word: "excellent".to_string(),
lemma: "excellent".to_string(),
positions: vec![(5, 14)],
frequency: 1.0,
word_senses: vec![],
semantic_features: vec!["positive".to_string()],
},
];
let relationships = vec![SemanticRelationship {
source_word: "good".to_string(),
target_word: "excellent".to_string(),
relationship_type: SemanticRelationshipType::Synonymy,
strength: 0.8,
confidence: 0.9,
distance: 0.2,
evidence: vec!["synonym_relationship".to_string()],
context_positions: vec![],
}];
let network = analyzer
.build_semantic_network(&lexical_items, &relationships)
.expect("operation should succeed");
assert_eq!(network.nodes.len(), 2);
assert_eq!(network.edges.len(), 1);
}
}