Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - ealdent/lda-ruby: A Ruby wrapper for Latent Dirichlet Allocation (LDA). · GitHub
Skip to content

Repository files navigation

Latent Dirichlet Allocation – Ruby Wrapper

What is LDA-Ruby?

This wrapper is based on C-code by David M. Blei. In a nutshell, it can be used to automatically cluster documents into topics. The number of topics are chosen beforehand and the topics found are usually fairly intuitive. Details of the implementation can be found in the paper by Blei, Ng, and Jordan.

The original C code relied on files for the input and output. We felt it was necessary to depart from that model and use Ruby objects for these steps instead. The only file necessary will be the data file (in a format similar to that used by SVMlight). Optionally you may need a vocabulary file to be able to extract the words belonging to topics.

Example usage:

require 'lda-ruby'
corpus = Lda::DataCorpus.new("data/data_file.dat")
lda = Lda::Lda.new(corpus) # create an Lda object for training
lda.em("random") # run EM algorithm using random starting points
lda.load_vocabulary("data/vocab.txt")
lda.print_topics(20) # print all topics with up to 20 words per topic

If you have general questions about Latent Dirichlet Allocation, I urge you to use the topic models mailing list, since the people who monitor that are very knowledgeable. If you encounter bugs specific to lda-ruby, please post an issue on the Github project.

Development

Local (Ruby 3.2+)

bundle install
bundle exec rake test

Docker (recommended for isolated setup)

./bin/docker-test

Rust backend runtime checks in Docker:

./bin/docker-test-rust

Install policy matrix checks in Docker:

./bin/docker-test-install-policies

For an interactive shell inside the dev container:

./bin/docker-shell

For an interactive shell with Rust toolchain + bindgen dependencies:

./bin/docker-shell-rust

Build tasks

  • bundle exec rake compile builds the native extension.
  • bundle exec rake compile_rust builds the experimental Rust extension and stages a Ruby-loadable artifact (lda_ruby_rust.<dlext>).
    • On macOS, build tasks automatically add the Rust linker flag for Ruby extension dynamic_lookup.
  • bundle exec rake test rebuilds the extension, then runs tests.
  • bundle exec rake build builds the gem package.
  • bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs backend compatibility fixtures.
  • LDA_RUBY_BACKEND=rust bundle exec ruby -Ilib:test test/backend_compatibility_test.rb runs parity checks in rust mode.
  • ./bin/benchmark-backends benchmarks available backends (pure, native, rust) and prints JSON.
  • ./bin/check-rust-benchmark enforces the Rust/pure benchmark ratio guardrail (configurable via env vars).
  • ./bin/docker-test-install-policies verifies packaged-gem install behavior for LDA_RUBY_RUST_BUILD=auto|always|never, including runtime EM smoke checks.
  • ./bin/test-packaged-gem-fallback verifies packaged-gem fallback behavior without Cargo (auto/never succeed, always fails) plus runtime smoke checks.
  • ./bin/test-packaged-gem-rust-enabled verifies packaged-gem behavior with Cargo available (auto/always enable Rust, never disables Rust) plus runtime smoke checks.
  • ./bin/test-packaged-gem-manifest verifies packaged-gem contents/metadata and rejects leaked build artifacts.
  • ./bin/release-preflight runs unit tests + packaged-gem validation stack; set SKIP_DOCKER=1 to skip Docker matrix checks.
  • ./bin/check-version-sync verifies version parity between VERSION.yml, lib/lda-ruby/version.rb, and expected release tag.
  • ./bin/verify-rubygems-api-key validates that your RubyGems API key can push non-interactively (required for CI release publishes).
  • ./bin/verify-release-publish --tag vX.Y.Z verifies published RubyGems + GitHub release assets for a release tag.
  • ./bin/release-prepare X.Y.Z updates version/changelog files for a new release version.
  • ./bin/release-artifacts --tag vX.Y.Z runs release checks, builds the source gem, and writes SHA256 checksums.
  • ./bin/release-precompiled-artifacts --tag vX.Y.Z --platform x86_64-linux --skip-preflight builds a precompiled platform gem and verifies install/runtime smoke checks.
    • The --platform value must match the current host platform.

Benchmark environment variables:

  • BENCH_RUNS (default: 3)
  • BENCH_START (default: seeded)
  • BENCH_TOPICS (default: 8)
  • BENCH_MAX_ITER (default: 20)
  • BENCH_EM_MAX_ITER (default: 40)

Install-time Rust build policy

Source installs now run both extension setup scripts (ext/lda-ruby/extconf.rb and ext/lda-ruby-rust/extconf.rb).

Rust build policy is controlled by LDA_RUBY_RUST_BUILD:

  • auto (default): build Rust extension if cargo is available, otherwise skip.
  • always: require Rust extension build and fail install if unavailable.
  • never: skip Rust extension build.

Examples:

  • LDA_RUBY_RUST_BUILD=always gem install lda-ruby
  • LDA_RUBY_RUST_BUILD=never bundle exec rake compile

Precompiled platform gems

Releases publish a source gem plus precompiled platform gems for:

  • x86_64-linux
  • x86_64-linux-musl
  • x86_64-darwin
  • arm64-darwin
  • x64-mingw-ucrt

On these platforms, installation should not require local C/Rust toolchains. Other platforms install from source gem and use the existing install-time fallback policy.

For artifact strategy, compatibility targets, and rollout/deprecation rules, see docs/precompiled-platform-policy.md.

Backend selection

  • Default mode is auto: Rust backend when available, otherwise native extension, otherwise pure Ruby.
  • Force pure Ruby backend:
    • Lda::Lda.new(corpus, backend: :pure)
    • or LDA_RUBY_BACKEND=pure
  • Force native backend:
    • Lda::Lda.new(corpus, backend: :native)
  • Force Rust backend (when extension is available):
    • Lda::Lda.new(corpus, backend: :rust)
    • or LDA_RUBY_BACKEND=rust

em("seeded") is supported by both native and pure backends for deterministic fixture-oriented runs.

Rust status: the extension hook layer is scaffolded in ext/lda-ruby-rust. Current Rust kernels include batched per-iteration corpus inference, batched per-document inference, topic-weights-per-word, topic-term-count accumulation, topic-term normalization/log-beta finalization, gamma-shift convergence reduction, topic-document average log-probability computation, seeded topic-term initialization, random topic-term initialization, and Rust-side EM orchestration paths (run_em, run_em_with_start, run_em_with_start_seed, session-based run_em_on_session_with_start_seed, settings-aware session run_em_on_session_start, and unified session-settings orchestration run_em_on_session) when backend: :rust is active. Session orchestration now uses shared Rust-side corpus storage and borrowed execution paths so EM session runs do not deep-clone corpus arrays per call, and the Ruby adapter now auto-recreates missing Rust sessions before EM to stay on the session path. The Rust backend still keeps the pure Ruby implementation as a compatibility fallback path if Rust orchestration is unavailable or returns invalid output. CI runs dedicated rust-runtime checks and numeric parity fixtures against the pure backend. compile_rust and LDA_RUBY_RUST_BUILD=always require a Rust toolchain plus Ruby development headers and libclang. Gem packaging excludes local Rust build artifacts (ext/lda-ruby-rust/target/**) so local cargo outputs do not leak into published gems.

Resources

References

Blei, David M., Ng, Andrew Y., and Jordan, Michael I. 2003. Latent dirichlet allocation. Journal of Machine Learning Research. 3 (Mar. 2003), 993-1022 [pdf].

Modernization

For a Ruby 3.2+/3.3+ porting proposal, see docs/porting-strategy.md.

For the latest implementation status and exact resume instructions, see docs/modernization-handoff.md.

For release steps and rollback guidance, see docs/release-runbook.md.

For precompiled gem strategy and compatibility policy, see docs/precompiled-platform-policy.md.

About

A Ruby wrapper for Latent Dirichlet Allocation (LDA).

Resources

Stars

134 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages