///|
/// A compact summary useful for diagnostics, monitoring, and benchmark output.
pub(all) struct CorpusStats {
records : Int
dimension : Int
zero_vectors : Int
duplicate_tokens : Int
min_norm : Double
max_norm : Double
}
///|
pub fn CorpusStats::describe(self : CorpusStats) -> String {
"records=\{self.records}, dimension=\{self.dimension}, zero_vectors=\{self.zero_vectors}, duplicate_tokens=\{self.duplicate_tokens}, min_norm=\{self.min_norm.to_string()}, max_norm=\{self.max_norm.to_string()}"
}
///|
pub fn EmbeddingCorpus::stats(self : EmbeddingCorpus) -> CorpusStats {
let seen : Map[String, Bool] = Map([])
let mut duplicates = 0
let mut zero_vectors = 0
let mut min_norm = 0.0
let mut max_norm = 0.0
for i, record in self.records {
if seen.contains(record.token) {
duplicates = duplicates + 1
}
seen.set(record.token, true)
let mut norm = 0.0
for value in record.vector {
norm = norm + value * value
}
norm = norm.sqrt()
if norm == 0.0 {
zero_vectors = zero_vectors + 1
}
if i == 0 || norm < min_norm {
min_norm = norm
}
if norm > max_norm {
max_norm = norm
}
}
{
records: self.records.length(),
dimension: self.dim,
zero_vectors,
duplicate_tokens: duplicates,
min_norm,
max_norm,
}
}
///|
/// Return the normalized vector for a token, if it exists.
pub fn EmbeddingCorpus::vector(
self : EmbeddingCorpus,
token : String,
) -> Array[Double]? {
self.lookup(token)
}
///|
/// Return all records whose token starts with `prefix`, preserving corpus order.
pub fn EmbeddingCorpus::prefix(
self : EmbeddingCorpus,
prefix : String,
limit : Int,
) -> Array[EmbeddingRecord] {
let result = []
if limit <= 0 {
return result
}
for record in self.records {
if result.length() >= limit {
break
}
if record.token.has_prefix(prefix) {
result.push(record)
}
}
result
}
///|
/// Return a copy of every vector, suitable for callers that need to mutate it.
pub fn EmbeddingCorpus::vectors(self : EmbeddingCorpus) -> Array[Array[Double]] {
let result = []
for record in self.records {
result.push(copy_vector(record.vector))
}
result
}
///|
/// Validate structural invariants without exposing internal maps.
pub fn EmbeddingCorpus::validate(self : EmbeddingCorpus) -> Bool {
for record in self.records {
if record.token.is_empty() || record.vector.length() != self.dim {
return false
}
}
true
}
///|
pub fn cosine_similarity(left : Array[Double], right : Array[Double]) -> Double {
let mut left_norm = 0.0
let mut right_norm = 0.0
let mut numerator = 0.0
let length = if left.length() < right.length() {
left.length()
} else {
right.length()
}
for i in 0.. Double {
1.0 - cosine_similarity(left, right)
}
///|
fn format_embedding_value(value : Double) -> String {
if value == 0.0 {
"0.0"
} else {
value.to_string()
}
}
///|
/// Serialize a corpus to a portable GloVe-style text representation.
pub fn EmbeddingCorpus::to_glove_text(self : EmbeddingCorpus) -> String {
let mut output = ""
for record in self.records {
output = output + record.token
for value in record.vector {
output = output + " " + format_embedding_value(value)
}
output = output + "\n"
}
output
}
///|
/// Serialize a corpus to word2vec text with an explicit header.
pub fn EmbeddingCorpus::to_word2vec_text(self : EmbeddingCorpus) -> String {
let mut output = "\{self.records.length()} \{self.dim}\n"
for record in self.records {
output = output + record.token
for value in record.vector {
output = output + " " + format_embedding_value(value)
}
output = output + "\n"
}
output
}
///|
pub fn SearchHit::above(self : SearchHit, threshold : Double) -> Bool {
self.score >= threshold
}
///|
pub fn SearchReport::best_score(self : SearchReport) -> Double? {
if self.hits.is_empty() {
None
} else {
Some(self.hits[0].score)
}
}
///|
pub fn SearchReport::tokens(self : SearchReport) -> Array[String] {
let result = []
for hit in self.hits {
result.push(hit.token)
}
result
}