nj.rs
Neighbor-Joining phylogenetic tree inference in Rust, with Python and WASM bindings.


Takes an aligned FASTA file as input and outputs a Newick string. Alphabet (DNA/protein) is auto-detected. Supports optional bootstrap support values on internal nodes.
Substitution models: PDiff (DNA + protein), JukesCantor (DNA), Kimura2P (DNA), Poisson (protein)
CLI
cargo install nj --features cli
nj sequences.fasta
nj --substitution-model kimura2-p --n-bootstrap-samples 100 sequences.fasta > tree.nwk
A progress bar is shown on stderr when bootstrapping.
Rust
[dependencies]
nj = "0.0.12"
use nj::{NJConfig, SequenceObject, nj, models::SubstitutionModel};
let newick = nj(NJConfig {
msa: vec![
SequenceObject { identifier: "A".into(), sequence: "ACGT".into() },
SequenceObject { identifier: "B".into(), sequence: "ACCT".into() },
],
n_bootstrap_samples: 100,
substitution_model: SubstitutionModel::JukesCantor,
}, Some(Box::new(|current, total| println!("{current}/{total}"))))?;
Python
pip install nj_py
from tqdm import tqdm
from nj_py import nj
config = {
"msa": [
{"identifier": "A", "sequence": "ACGT"},
{"identifier": "B", "sequence": "ACCT"},
],
"n_bootstrap_samples": 100,
"substitution_model": "JukesCantor",
}
with tqdm(total=100) as progress_bar:
newick = nj(config, on_progress=lambda current, total: progress_bar.update(1))
JavaScript / WASM
npm install @holmrenser/nj
import { nj } from '@holmrenser/nj';
const config = {
msa: [
{ identifier: 'A', sequence: 'ACGT' },
{ identifier: 'B', sequence: 'ACCT' },
],
n_bootstrap_samples: 100,
substitution_model: 'JukesCantor',
};
const newick = nj(config, (current, total) => {
progressBar.value = current / total * 100;
});