diff --git a/src/bin/aphid/main.rs b/src/bin/aphid/main.rs
index a464b6b7e5edbde1414164f1259bcc296b763b1e..e88d39866e44f1cd6d7ac9d24a1ddae6bb640b9b 100644
--- a/src/bin/aphid/main.rs
+++ b/src/bin/aphid/main.rs
@@ -155,10 +155,10 @@ fn run() -> Result<(), Error> {
     //==============================================================================
     // Extract various information from the trees,
     // and handle data from them with some kind of 'dataframe' pattern:
-    // an informal sequence of same-sized columns.
+    // an informal collection of same-sized columns.
     let ids = forest.ids();
     let raw_trees = forest.trees();
-    let raw_nodes = raw_trees.iter().map(GeneTree::len).collect::<Vec<_>>();
+    let raw_n_nodes = raw_trees.iter().map(GeneTree::len).collect::<Vec<_>>();
     let sequences_lengths = raw_trees
         .iter()
         .map(GeneTree::sequence_length)
@@ -166,13 +166,13 @@ fn run() -> Result<(), Error> {
 
     //----------------------------------------------------------------------------------------------
     eprintln!("Prune trees so they only contain relevant species.");
-    let (pruned_trees, pruned_nodes) = raw_trees.iter().fold(
+    let (pruned_trees, pruned_n_nodes) = raw_trees.iter().fold(
         (Vec::new(), Vec::new()),
-        |(mut trees, mut nodes), raw_tree| {
+        |(mut trees, mut n_nodes), raw_tree| {
             let pruned = raw_tree.pruned(|_, leaf| relevant_species.contains(&leaf.payload));
-            nodes.push(pruned.len());
+            n_nodes.push(pruned.len());
             trees.push(pruned);
-            (trees, nodes)
+            (trees, n_nodes)
         },
     );
 
@@ -256,8 +256,7 @@ fn run() -> Result<(), Error> {
     //----------------------------------------------------------------------------------------------
     // Geometry filter.
 
-    // Is is actually needed?
-    let geometry_pass = max_clock_ratio.is_some();
+    let geometry_pass = max_clock_ratio.is_some(); // (TODO: is this flag actually needed?)
 
     if geometry_pass {
         separator();
@@ -352,8 +351,8 @@ fn run() -> Result<(), Error> {
     for i in 0..ids.len() {
         let id = ids[i];
         let sl = &sequences_lengths[i];
-        let n = &raw_nodes[i];
-        let np = &pruned_nodes[i];
+        let n = &raw_n_nodes[i];
+        let np = &pruned_n_nodes[i];
         let otrip = &passed_triplets[i];
         let out = passed_outgroup[i];
         let top = passed_top[i];
@@ -472,7 +471,7 @@ fn run() -> Result<(), Error> {
         .map(|pass| u64::from(!pass))
         .sum::<u64>();
     eprintln!(
-        "  - {} outroup{} rejected (empty or non-monophyletic).",
+        "  - {} outgroup{} rejected (empty or non-monophyletic).",
         n,
         s(n)
     );
@@ -545,10 +544,7 @@ fn run() -> Result<(), Error> {
     eprintln!("total: {lnl}");
 
     eprintln!("\nOptimizing ln-likelihood:");
-    let (opt, opt_lnl) =
-        optimize_likelihood(&triplets, parms, &config.search).unwrap_or_else(|e| {
-            panic!("TODO: once learning works, remove all error types in favour of snafu.\n{e}")
-        });
+    let (opt, opt_lnl) = optimize_likelihood(&triplets, parms, &config.search)?;
 
     eprintln!("\nOptimized ln-likelihood:");
     #[cfg(test)]
@@ -596,4 +592,6 @@ enum Error {
     },
     #[snafu(display("Inconsistent input:\n{mess}"))]
     InputConsistency { mess: String },
+    #[snafu(transparent)]
+    Learn { source: aphid::learn::Error },
 }
diff --git a/src/tree.rs b/src/tree.rs
index 4eea031bae3cd17d41ad42dae37ca4b9569b6336..6978dea550caacea5c73e255d17415282a8c7c63 100644
--- a/src/tree.rs
+++ b/src/tree.rs
@@ -22,7 +22,8 @@ pub(crate) mod prune;
 
 #[cfg_attr(test, derive(Debug, PartialEq))]
 pub(crate) struct Tree<B, IN, TN> {
-    pub(crate) nodes: Vec<Node<B, IN, TN>>, // Stored in order of appearance in original Newick input.
+    // Stored in pre-order corresponding to appearance order in original Newick input.
+    pub(crate) nodes: Vec<Node<B, IN, TN>>,
 }
 
 #[cfg_attr(test, derive(Debug, PartialEq))]