summaryrefslogtreecommitdiff
path: root/lib/classifier/lsi/content_node.rb
diff options
authorSimon Quigley <tsimonq2@debian.org>2026-06-26 15:36:16 -0500
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2026-06-27 04:38:44 +0000
commit464cd1abd409468939e65d756cd66fb229db0597 (patch)
tree4a04e699f4ecd427d25742d1efc65429bf2a724c /lib/classifier/lsi/content_node.rb
parentee607acdb2855c00c0904b2805221eabcf063f3b (diff)
Imported using git-ubuntu import.
Notes
Notes: * Team upload. [ Debian Janitor ] * Set upstream metadata fields: Bug-Submit. * Update standards version to 4.4.1, no changes needed. * Update watch file format version to 4. * Bump debhelper from old 12 to 13. * Update standards version to 4.5.1, no changes needed. * Update standards version to 4.6.1, no changes needed. [ Cédric Boutillier ] * Update team name * Add .gitattributes to keep unwanted files out of the source package [ Lucas Nussbaum ] * debian/gbp.conf: Add for DEP-14 * debian/gbp.conf: remove trailing empty lines * debian/.gitattributes: remove * debian/salsa-ci.yml: use team-specific include [ Simon Quigley ] * Upgrade the watch file to version 5. * New upstream release. * Refresh the upstream metadata. * Refresh the rules file. * Drop {XS,XB}-Ruby-Versions from control. * Update Standards-Version to 4.7.4. * Bump debhelper-compat to 14, dropping ${misc:Depends}, ${shlibs:Depends}, and ${ruby:Depends} from runtime dependencies. * Mark the package as Architecture: any. * Add Forwarded: not-needed to the patch.
Diffstat (limited to 'lib/classifier/lsi/content_node.rb')
-rw-r--r--lib/classifier/lsi/content_node.rb102
1 files changed, 63 insertions, 39 deletions
diff --git a/lib/classifier/lsi/content_node.rb b/lib/classifier/lsi/content_node.rb
index f313331..37a9448 100644
--- a/lib/classifier/lsi/content_node.rb
+++ b/lib/classifier/lsi/content_node.rb
@@ -1,72 +1,96 @@
+# rbs_inline: enabled
+
# Author:: David Fayram (mailto:dfayram@lensmen.net)
# Copyright:: Copyright (c) 2005 David Fayram II
# License:: LGPL
module Classifier
-
-# This is an internal data structure class for the LSI node. Save for
-# raw_vector_with, it should be fairly straightforward to understand.
-# You should never have to use it directly.
+ # This is an internal data structure class for the LSI node. Save for
+ # raw_vector_with, it should be fairly straightforward to understand.
+ # You should never have to use it directly.
class ContentNode
- attr_accessor :raw_vector, :raw_norm,
- :lsi_vector, :lsi_norm,
- :categories
-
+ # @rbs @word_hash: Hash[Symbol, Integer]
+
+ # @rbs @raw_vector: untyped
+ # @rbs @raw_norm: untyped
+ # @rbs @lsi_vector: untyped
+ # @rbs @lsi_norm: untyped
+ attr_accessor :raw_vector, :raw_norm, :lsi_vector, :lsi_norm
+
+ # @rbs @categories: Array[String | Symbol]
+ attr_accessor :categories
+
attr_reader :word_hash
+
# If text_proc is not specified, the source will be duck-typed
# via source.to_s
- def initialize( word_hash, *categories )
+ #
+ # @rbs (Hash[Symbol, Integer], *String | Symbol) -> void
+ def initialize(word_frequencies, *categories)
@categories = categories || []
- @word_hash = word_hash
+ @word_hash = word_frequencies
end
-
+
# Use this to fetch the appropriate search vector.
+ #
+ # @rbs () -> untyped
def search_vector
@lsi_vector || @raw_vector
end
-
+
# Use this to fetch the appropriate search vector in normalized form.
+ #
+ # @rbs () -> untyped
def search_norm
@lsi_norm || @raw_norm
end
-
+
# Creates the raw vector out of word_hash using word_list as the
# key for mapping the vector space.
- def raw_vector_with( word_list )
- if $GSL
- vec = GSL::Vector.alloc(word_list.size)
- else
- vec = Array.new(word_list.size, 0)
- end
+ #
+ # @rbs (WordList) -> untyped
+ def raw_vector_with(word_list)
+ vec = if Classifier::LSI.native_available?
+ Classifier::LSI.vector_class.alloc(word_list.size)
+ else
+ Array.new(word_list.size, 0)
+ end
@word_hash.each_key do |word|
vec[word_list[word]] = @word_hash[word] if word_list[word]
end
-
+
# Perform the scaling transform
- total_words = vec.sum
-
+ total_words = Classifier::LSI.native_available? ? vec.sum : vec.sum_with_identity
+ vec_array = Classifier::LSI.native_available? ? vec.to_a : vec
+ total_unique_words = vec_array.count { |word| word != 0 }
+
# Perform first-order association transform if this vector has more
- # than one word in it.
- if total_words > 1.0
+ # than one word in it.
+ if total_words > 1.0 && total_unique_words > 1
weighted_total = 0.0
+
vec.each do |term|
- if ( term > 0 )
- weighted_total += (( term / total_words ) * Math.log( term / total_words ))
- end
- end
- vec = vec.collect { |val| Math.log( val + 1 ) / -weighted_total }
+ next unless term.positive?
+ next if total_words.zero?
+
+ term_over_total = term / total_words
+ val = term_over_total * Math.log(term_over_total)
+ weighted_total += val unless val.nan?
+ end
+
+ sign = weighted_total.negative? ? 1.0 : -1.0
+ divisor = sign * [weighted_total.abs, Vector::EPSILON].max
+ vec = vec.collect { |val| Math.log(val + 1) / divisor }
end
-
- if $GSL
- @raw_norm = vec.normalize
- @raw_vector = vec
+
+ if Classifier::LSI.native_available?
+ @raw_norm = vec.normalize
+ @raw_vector = vec
else
- @raw_norm = Vector[*vec].normalize
- @raw_vector = Vector[*vec]
+ @raw_norm = Vector[*vec].normalize
+ @raw_vector = Vector[*vec]
end
- end
-
- end
-
+ end
+ end
end